@myagentroam/node 0.9.139 → 0.9.140

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
@@ -316,7 +316,7 @@ export class NodeConnector {
316
316
  waitMs
317
317
  })
318
318
  });
319
- workspaceFileService = new WorkspaceFileService(this.workspaceState, () => this.config?.allowedRoots ?? [], (workspaceId) => {
319
+ workspaceFileService = new WorkspaceFileService(this.workspaceState, (workspaceId) => {
320
320
  this.workspaceWorkbenchService?.invalidateWorkspaceFiles(workspaceId);
321
321
  this.workspaceCoordinator.invalidate(workspaceId);
322
322
  });
@@ -462,6 +462,7 @@ export class NodeConnector {
462
462
  sequence: this.workbenchEventService.snapshotSequence(),
463
463
  session: this.sessionPresentationService.present(session),
464
464
  runs: this.runtime.listRunsForSession(session.id),
465
+ performance: this.runtime.sessionPerformance(session.id),
465
466
  queue: this.workspaceQueueWorkbenchService.snapshot(session.id),
466
467
  context,
467
468
  conversation: this.conversationSegmentService.projectInitial(session, page, limit)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  // Keep the CLI implementation in its own package; the Node install exposes the same entrypoint.
3
- const entrypoint = '@myagentroam/marmgr/bin/marmgr.mjs';
3
+ const entrypoint = '@myagentroam/marctl/bin/marctl.mjs';
4
4
  await import(entrypoint);
5
5
  export {};
package/dist/marnodes.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- const entrypoint = '@myagentroam/marmgr/bin/marnodes.mjs';
2
+ const entrypoint = '@myagentroam/marctl/bin/marnodes.mjs';
3
3
  await import(entrypoint);
4
4
  export {};
package/dist/marscp.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- const entrypoint = '@myagentroam/marmgr/bin/marscp.mjs';
2
+ const entrypoint = '@myagentroam/marctl/bin/marscp.mjs';
3
3
  await import(entrypoint);
4
4
  export {};
package/dist/marssh.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- const entrypoint = '@myagentroam/marmgr/bin/marssh.mjs';
2
+ const entrypoint = '@myagentroam/marctl/bin/marssh.mjs';
3
3
  await import(entrypoint);
4
4
  export {};
@@ -172,17 +172,17 @@ export class AbstractRunner {
172
172
  function resolvedMcpServers(raw, secrets) {
173
173
  if (!Array.isArray(raw))
174
174
  return [];
175
- const marmgrEnvironment = {
175
+ const marctlEnvironment = {
176
176
  MAR_USERNAME: secrets['MAR_USERNAME'] ?? '',
177
- ...(secrets['MAR_MGR_SERVER_URL'] === undefined
177
+ ...(secrets['MAR_CTL_SERVER_URL'] === undefined
178
178
  ? {}
179
- : { MAR_MGR_SERVER_URL: secrets['MAR_MGR_SERVER_URL'] }),
180
- ...(secrets['MAR_MGR_USER_TOKEN'] === undefined
179
+ : { MAR_CTL_SERVER_URL: secrets['MAR_CTL_SERVER_URL'] }),
180
+ ...(secrets['MAR_CTL_USER_TOKEN'] === undefined
181
181
  ? {}
182
- : { MAR_MGR_USER_TOKEN: secrets['MAR_MGR_USER_TOKEN'] }),
183
- MAR_MGR_CONTEXT_REQUIRED: '1'
182
+ : { MAR_CTL_USER_TOKEN: secrets['MAR_CTL_USER_TOKEN'] }),
183
+ MAR_CTL_CONTEXT_REQUIRED: '1'
184
184
  };
185
- const mcpEnvironment = (provided) => nodeExecutionEnvironment({ ...provided, ...marmgrEnvironment }, { PATH: process.env['PATH'], Path: process.env['Path'] });
185
+ const mcpEnvironment = (provided) => nodeExecutionEnvironment({ ...provided, ...marctlEnvironment }, { PATH: process.env['PATH'], Path: process.env['Path'] });
186
186
  const result = [];
187
187
  for (const value of raw) {
188
188
  if (value === null || typeof value !== 'object' || Array.isArray(value))
@@ -173,7 +173,7 @@ export class ClaudeManagedRunController {
173
173
  ...(typeof payload.access === 'string' ? { access: payload.access } : {}),
174
174
  environment: nodeExecutionEnvironment({
175
175
  ...secretEnvironment,
176
- MAR_MGR_CONTEXT_REQUIRED: '1'
176
+ MAR_CTL_CONTEXT_REQUIRED: '1'
177
177
  }, { PATH: process.env['PATH'], Path: process.env['Path'] }),
178
178
  ...(typeof payload.personalInstructions === 'string'
179
179
  ? { personalInstructions: payload.personalInstructions }
@@ -66,7 +66,7 @@ export class CodexManagedRunController {
66
66
  throw new Error('RUNNER_COMMAND_UNAVAILABLE');
67
67
  if (command.id === 'compact') {
68
68
  const runId = crypto.randomUUID();
69
- this.runner.prepareEnvironment(nodeExecutionEnvironment({ ...context.secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' }, { PATH: process.env['PATH'], Path: process.env['Path'] }), runId);
69
+ this.runner.prepareEnvironment(nodeExecutionEnvironment({ ...context.secretEnvironment, MAR_CTL_CONTEXT_REQUIRED: '1' }, { PATH: process.env['PATH'], Path: process.env['Path'] }), runId);
70
70
  try {
71
71
  await this.runner.start(runId);
72
72
  return await this.compact(session, threadId, runId, context.initiatedByUserId);
@@ -386,7 +386,7 @@ export class CodexManagedRunController {
386
386
  return;
387
387
  }
388
388
  try {
389
- this.runner.prepareEnvironment(nodeExecutionEnvironment({ ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' }, { PATH: process.env['PATH'], Path: process.env['Path'] }), runId);
389
+ this.runner.prepareEnvironment(nodeExecutionEnvironment({ ...secretEnvironment, MAR_CTL_CONTEXT_REQUIRED: '1' }, { PATH: process.env['PATH'], Path: process.env['Path'] }), runId);
390
390
  }
391
391
  catch (error) {
392
392
  this.host.emit(runId, 'run.rejected', { code: safeErrorCode(error, 'SECRET_ENVIRONMENT_INVALID') }, 'FAILED');
@@ -141,9 +141,14 @@ export class MarAgentConversationNormalizer {
141
141
  }
142
142
  #toolStarted(event) {
143
143
  const ownItemId = itemId('tool', event.callId);
144
- if (event.toolName === 'exec') {
144
+ if (event.toolName === 'exec' || event.toolName === 'exec_wait') {
145
145
  const input = record(event.input) ?? {};
146
- const action = input.action == null && typeof input.command === 'string' ? 'start' : input.action;
146
+ const action = input.action ??
147
+ (event.toolName === 'exec_wait'
148
+ ? 'poll'
149
+ : typeof input.command === 'string'
150
+ ? 'start'
151
+ : undefined);
147
152
  if (action === 'start' && typeof input.command === 'string') {
148
153
  const command = {
149
154
  itemId: ownItemId,
@@ -227,7 +232,8 @@ export class MarAgentConversationNormalizer {
227
232
  }
228
233
  #toolCompleted(event) {
229
234
  const state = this.#tools.get(event.callId);
230
- if (event.toolName === 'exec' && state?.commandOrigin !== undefined) {
235
+ if ((event.toolName === 'exec' || event.toolName === 'exec_wait') &&
236
+ state?.commandOrigin !== undefined) {
231
237
  const command = this.#commands.get(state.commandOrigin);
232
238
  const result = execResult(event.data, event.summary, event.truncated === true);
233
239
  if (command !== undefined && result !== undefined) {
@@ -293,7 +299,8 @@ export class MarAgentConversationNormalizer {
293
299
  }
294
300
  #toolFailed(event) {
295
301
  const state = this.#tools.get(event.callId);
296
- if (event.toolName === 'exec' && state?.commandOrigin !== undefined) {
302
+ if ((event.toolName === 'exec' || event.toolName === 'exec_wait') &&
303
+ state?.commandOrigin !== undefined) {
297
304
  const command = this.#commands.get(state.commandOrigin);
298
305
  const input = record(state.input);
299
306
  if (command !== undefined &&
@@ -273,7 +273,9 @@ export class MarAgentManagedRunController {
273
273
  throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
274
274
  runtime.activeRunId = input.runId;
275
275
  await runtime.mcpLease.pool.refreshForExecution();
276
- const executionMcpServers = input.mcpServers.filter((server) => server.lifecycle === 'EXECUTION');
276
+ const executionMcpServers = input.mcpServers
277
+ .filter(isExecutionScopedMcp)
278
+ .map((server) => ({ ...server, lifecycle: 'EXECUTION' }));
277
279
  computerUseBinding = await this.#computerUse?.bind({
278
280
  runId: input.runId,
279
281
  sessionId: runtime.session.id,
@@ -311,6 +313,9 @@ export class MarAgentManagedRunController {
311
313
  });
312
314
  executionMcpBinding = runtime.host.bindExecutionExtensions({
313
315
  ...imageExtensions,
316
+ environment: input.secretEnvironment?.['MAR_CTL_USER_TOKEN'] === undefined
317
+ ? {}
318
+ : { MAR_CTL_USER_TOKEN: input.secretEnvironment['MAR_CTL_USER_TOKEN'] },
314
319
  skills: [...(imageExtensions.skills ?? []), computerUseHostSkill]
315
320
  }, {
316
321
  tools: [...nodeTools, ...userTools],
@@ -500,7 +505,7 @@ export class MarAgentManagedRunController {
500
505
  async #createRuntime(input, sessionKey, fingerprint, hostConfiguration) {
501
506
  const dynamicCredentials = input.models.some((model) => model.credential?.type === 'RUNTIME_TICKET' ||
502
507
  model.imageGeneration?.credential?.type === 'RUNTIME_TICKET');
503
- const sharedMcpServers = input.mcpServers.filter((server) => server.lifecycle !== 'EXECUTION');
508
+ const sharedMcpServers = input.mcpServers.filter((server) => !isExecutionScopedMcp(server));
504
509
  const mcpLease = await this.#mcpClients.acquire(sharedMcpServers, input.cwd, {
505
510
  // Native Session IDs are assigned after the first lease; do not let that promotion
506
511
  // change a confirmed actor's discovery identity when its warm runtime is recreated.
@@ -706,6 +711,10 @@ function contextUsageChanged(event) {
706
711
  (event.type === 'usage.updated' &&
707
712
  (event.contextInputTokens !== undefined || event.inputTokens !== undefined)));
708
713
  }
714
+ function isExecutionScopedMcp(server) {
715
+ return (server.lifecycle === 'EXECUTION' ||
716
+ (server.transport === 'STDIO' && server.environment?.['MAR_CTL_USER_TOKEN'] !== undefined));
717
+ }
709
718
  function sessionRuntimeFingerprint(input, hostConfiguration) {
710
719
  return createHash('sha256')
711
720
  .update(JSON.stringify({
@@ -713,7 +722,7 @@ function sessionRuntimeFingerprint(input, hostConfiguration) {
713
722
  defaultModelId: input.model.id,
714
723
  models: input.models.map(runtimeModelFingerprint),
715
724
  personalInstructions: input.personalInstructions ?? '',
716
- mcpServers: input.mcpServers.filter((server) => server.lifecycle !== 'EXECUTION'),
725
+ mcpServers: input.mcpServers.filter((server) => !isExecutionScopedMcp(server)),
717
726
  environment: Object.entries(hostConfiguration.environment)
718
727
  .filter((entry) => entry[1] !== undefined)
719
728
  .sort(([left], [right]) => left.localeCompare(right)),
@@ -727,8 +736,10 @@ export function allowsUntrustedWebFetchTls(environment) {
727
736
  async function resolveMarAgentHostRuntimeConfiguration(secretEnvironment = {}) {
728
737
  const environment = nodeExecutionEnvironment({
729
738
  ...secretEnvironment,
730
- MAR_MGR_CONTEXT_REQUIRED: '1'
739
+ MAR_CTL_CONTEXT_REQUIRED: '1'
731
740
  });
741
+ // CLI credentials are bound to each execution, never retained as Host configuration.
742
+ delete environment['MAR_CTL_USER_TOKEN'];
732
743
  const ripgrep = await availableBundledRipgrep();
733
744
  return {
734
745
  environment: ripgrep === undefined ? environment : prependBundledRipgrepToPath(environment, ripgrep),
@@ -50,7 +50,7 @@ export class OpenCodeManagedRunController {
50
50
  this.host.hasActiveSessionLease(session.id))
51
51
  throw new Error('RUNNER_COMMAND_UNAVAILABLE');
52
52
  const runId = crypto.randomUUID();
53
- const client = this.runner.managedClient(nodeExecutionEnvironment({ ...environment, MAR_MGR_CONTEXT_REQUIRED: '1' }, { PATH: process.env['PATH'], Path: process.env['Path'] }));
53
+ const client = this.runner.managedClient(nodeExecutionEnvironment({ ...environment, MAR_CTL_CONTEXT_REQUIRED: '1' }, { PATH: process.env['PATH'], Path: process.env['Path'] }));
54
54
  this.host.adopt({
55
55
  runId,
56
56
  sessionId: session.id,
@@ -111,7 +111,7 @@ export class OpenCodeManagedRunController {
111
111
  try {
112
112
  environment = nodeExecutionEnvironment({
113
113
  ...parseSecretEnvironment(payload.secretEnvironment),
114
- MAR_MGR_CONTEXT_REQUIRED: '1'
114
+ MAR_CTL_CONTEXT_REQUIRED: '1'
115
115
  }, { PATH: process.env['PATH'], Path: process.env['Path'] });
116
116
  mcpSecretEnvironment = parseSecretEnvironment(payload.mcpSecretEnvironment);
117
117
  mcp = this.runner.mcpConfiguration(payload.mcpInstallations, {
@@ -13,6 +13,7 @@ export class NodeRuntimeState {
13
13
  static MAX_COMPLETED_TURNS_PER_SESSION = 10;
14
14
  sessions = new Map();
15
15
  runs = new Map();
16
+ runPerformance = new Map();
16
17
  recentUserMessages = new Map();
17
18
  messageSubmissions = new Map();
18
19
  failedMessageSubmissions = new Map();
@@ -553,6 +554,21 @@ export class NodeRuntimeState {
553
554
  const canonical = this.executions.get(id)?.runId ?? id;
554
555
  return this.runs.get(canonical);
555
556
  }
557
+ /** Only the current execution may replace an active Run's ephemeral metrics. */
558
+ updateRunPerformance(id, performance) {
559
+ const run = this.getRun(id);
560
+ if (run === undefined || terminal(run.status) || performance.runId !== run.id)
561
+ return false;
562
+ if (id !== run.id && this.currentExecutionId(run.id) !== id)
563
+ return false;
564
+ this.runPerformance.set(run.id, { ...performance });
565
+ return true;
566
+ }
567
+ sessionPerformance(sessionId) {
568
+ const run = this.activeSessionRun(sessionId);
569
+ const performance = run === undefined ? undefined : this.runPerformance.get(run.id);
570
+ return performance === undefined ? undefined : { ...performance };
571
+ }
556
572
  findMessageRun(sessionId, clientMessageId) {
557
573
  const submission = this.messageSubmissions.get(`${sessionId}\u0000${clientMessageId}`);
558
574
  return submission?.kind === 'run' ? this.runs.get(submission.id) : undefined;
@@ -599,6 +615,8 @@ export class NodeRuntimeState {
599
615
  updatedAt: now
600
616
  };
601
617
  this.runs.set(runId, value);
618
+ if (terminal(next))
619
+ this.runPerformance.delete(runId);
602
620
  this.emitRunChanged(value, this.requireSession(value.sessionId));
603
621
  if (terminal(next) && this.leases.get(value.sessionId) === value.id)
604
622
  this.leases.delete(value.sessionId);
@@ -894,6 +912,7 @@ export class NodeRuntimeState {
894
912
  }
895
913
  this.pruneRetainedExecutionTurns(run.sessionId, retainedTurnIds);
896
914
  this.runs.delete(runId);
915
+ this.runPerformance.delete(runId);
897
916
  this.recentUserMessages.delete(runId);
898
917
  this.runSequences.delete(runId);
899
918
  for (const [key, candidate] of this.messageSubmissions)
@@ -931,6 +950,7 @@ export class NodeRuntimeState {
931
950
  this.leases.delete(sessionId);
932
951
  for (const runId of runIds) {
933
952
  this.runs.delete(runId);
953
+ this.runPerformance.delete(runId);
934
954
  this.recentUserMessages.delete(runId);
935
955
  this.runSequences.delete(runId);
936
956
  }
@@ -994,6 +1014,7 @@ export class NodeRuntimeState {
994
1014
  }
995
1015
  for (const runId of runIds) {
996
1016
  this.runs.delete(runId);
1017
+ this.runPerformance.delete(runId);
997
1018
  this.recentUserMessages.delete(runId);
998
1019
  this.runSequences.delete(runId);
999
1020
  }
@@ -15,7 +15,7 @@ export class RunEventService {
15
15
  if (run === undefined || !isPlainRecord(payload))
16
16
  return;
17
17
  const parsed = runPerformanceSchema.safeParse({ ...payload, runId: run.id });
18
- if (parsed.success) {
18
+ if (parsed.success && this.options.runtime.updateRunPerformance(runId, parsed.data)) {
19
19
  this.flushText(runId);
20
20
  this.options.emitWorkbench('conversation', {
21
21
  sessionId: run.sessionId,
@@ -61,7 +61,7 @@ export class TerminalService {
61
61
  const options = {
62
62
  ...input,
63
63
  workspaceId: typeof input.workspaceId === 'string' ? input.workspaceId : null,
64
- environment: { ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' },
64
+ environment: { ...secretEnvironment, MAR_CTL_CONTEXT_REQUIRED: '1' },
65
65
  ...(cwd === undefined ? {} : { cwd })
66
66
  };
67
67
  const terminal = commandMode
@@ -1,16 +1,13 @@
1
- import { tmpdir } from 'node:os';
2
1
  import { isAbsolute, relative, resolve, win32 } from 'node:path';
3
2
  import { realpath } from 'node:fs/promises';
4
- import { listWorkspaceFiles, mutateWorkspaceFile, normalizeFilePreviewPath, readAllowedFileContentRange, readWorkspaceFileContentRange, readAllowedTextFile, readWorkspaceTextFile, writeWorkspaceTextFile, WorkspaceFileIndex } from '../workspace.js';
3
+ import { listWorkspaceFiles, mutateWorkspaceFile, normalizeFilePreviewPath, readAbsoluteFileContentRange, readWorkspaceFileContentRange, readAbsoluteTextFile, readWorkspaceTextFile, writeWorkspaceTextFile, WorkspaceFileIndex } from '../workspace.js';
5
4
  const INDEX_CACHE_LIMIT = 32;
6
5
  export class WorkspaceFileService {
7
6
  state;
8
- allowedRoots;
9
7
  onInvalidated;
10
8
  mutationTails = new Map();
11
- constructor(state, allowedRoots, onInvalidated) {
9
+ constructor(state, onInvalidated) {
12
10
  this.state = state;
13
- this.allowedRoots = allowedRoots;
14
11
  this.onInvalidated = onInvalidated;
15
12
  }
16
13
  async list(workspace, input) {
@@ -38,7 +35,7 @@ export class WorkspaceFileService {
38
35
  if (workspacePath !== workspace.path && isFilePreviewAbsolutePath(path))
39
36
  throw new Error('FILE_PATH_ESCAPE');
40
37
  return isFilePreviewAbsolutePath(path)
41
- ? readAllowedTextFile(path, this.previewRoots(), offset, limit)
38
+ ? readAbsoluteTextFile(path, offset, limit)
42
39
  : readWorkspaceTextFile(workspacePath, path, offset, limit);
43
40
  }
44
41
  async readContent(workspace, input, workspacePath = workspace.path) {
@@ -69,7 +66,7 @@ export class WorkspaceFileService {
69
66
  const offset = typeof input.offset === 'number' ? input.offset : 0;
70
67
  const limit = typeof input.limit === 'number' ? input.limit : 512 * 1024;
71
68
  return isFilePreviewAbsolutePath(path)
72
- ? readAllowedFileContentRange(path, this.previewRoots(), offset, limit)
69
+ ? readAbsoluteFileContentRange(path, offset, limit)
73
70
  : readWorkspaceFileContentRange(workspacePath, path, offset, limit);
74
71
  }
75
72
  readDirect(workspace, input, workspacePath = workspace.path) {
@@ -218,9 +215,6 @@ export class WorkspaceFileService {
218
215
  index.dispose();
219
216
  this.state.fileIndexes.clear();
220
217
  }
221
- previewRoots() {
222
- return [...new Set([...this.allowedRoots(), tmpdir()])];
223
- }
224
218
  }
225
219
  function isFilePreviewAbsolutePath(path) {
226
220
  return isAbsolute(path) || win32.isAbsolute(path);
package/dist/workspace.js CHANGED
@@ -757,15 +757,15 @@ export async function readWorkspaceFileContentRange(workspaceRoot, requestedPath
757
757
  await handle.close();
758
758
  }
759
759
  }
760
- /** Reads one absolute file range only when the resolved target remains within a preview root. */
761
- export async function readAllowedFileContentRange(requestedPath, allowedRoots, offset = 0, limit = FILE_READ_LIMIT) {
760
+ /** Reads one regular absolute file range using the Node account's filesystem permissions. */
761
+ export async function readAbsoluteFileContentRange(requestedPath, offset = 0, limit = FILE_READ_LIMIT) {
762
762
  if (!Number.isSafeInteger(offset) ||
763
763
  offset < 0 ||
764
764
  !Number.isSafeInteger(limit) ||
765
765
  limit < 1 ||
766
766
  limit > FILE_READ_LIMIT)
767
767
  throw new Error('FILE_RANGE_INVALID');
768
- const { file, metadata } = await resolveAllowedFile(requestedPath, allowedRoots);
768
+ const { file, metadata } = await resolveAbsoluteFile(requestedPath);
769
769
  if (offset > metadata.size)
770
770
  throw new Error('FILE_RANGE_INVALID');
771
771
  const length = Math.min(limit, metadata.size - offset);
@@ -792,9 +792,9 @@ function fileSourceRevision(metadata) {
792
792
  .update([metadata.dev, metadata.ino, metadata.size, metadata.mtimeMs, metadata.ctimeMs].join(':'))
793
793
  .digest('hex');
794
794
  }
795
- /** Reads one absolute file only when the resolved target remains within a preview root. */
796
- export async function readAllowedTextFile(requestedPath, allowedRoots, offset = 0, limit = FILE_READ_LIMIT) {
797
- const { file, metadata } = await resolveAllowedFile(requestedPath, allowedRoots);
795
+ /** Reads one regular absolute file using the Node account's filesystem permissions. */
796
+ export async function readAbsoluteTextFile(requestedPath, offset = 0, limit = FILE_READ_LIMIT) {
797
+ const { file, metadata } = await resolveAbsoluteFile(requestedPath);
798
798
  if (metadata.size > FILE_READ_LIMIT * 16)
799
799
  throw new Error('FILE_TOO_LARGE');
800
800
  if (!Number.isSafeInteger(offset) ||
@@ -859,7 +859,7 @@ export function normalizeFilePreviewPath(requestedPath) {
859
859
  throw new Error('FILE_PATH_ESCAPE');
860
860
  return normalized;
861
861
  }
862
- async function resolveAllowedFile(requestedPath, allowedRoots) {
862
+ async function resolveAbsoluteFile(requestedPath) {
863
863
  if (!isAbsolute(requestedPath))
864
864
  throw new Error('FILE_PATH_ESCAPE');
865
865
  const file = await realpath(requestedPath).catch((error) => {
@@ -867,9 +867,6 @@ async function resolveAllowedFile(requestedPath, allowedRoots) {
867
867
  throw new Error('FILE_NOT_FOUND');
868
868
  throw error;
869
869
  });
870
- const roots = await Promise.all(allowedRoots.map((root) => realpath(root)));
871
- if (!isWithinAllowedRoot(file, roots))
872
- throw new Error('FILE_PATH_ESCAPE');
873
870
  const metadata = await lstat(file);
874
871
  if (!metadata.isFile())
875
872
  throw new Error('FILE_NOT_REGULAR');
@@ -1019,7 +1016,9 @@ export function normalizeWorkspacePath(input, platform = hostPlatform()) {
1019
1016
  if (!win32.isAbsolute(normalized)) {
1020
1017
  throw new Error('WORKSPACE_PATH_NOT_ABSOLUTE');
1021
1018
  }
1022
- return normalized.replace(/\\+$/, '').toLowerCase();
1019
+ // A drive root must remain absolute instead of becoming drive-relative.
1020
+ const root = win32.parse(normalized).root;
1021
+ return (normalized === root ? root : normalized.replace(/\\+$/, '')).toLowerCase();
1023
1022
  }
1024
1023
  if (platform !== 'linux' && platform !== 'darwin') {
1025
1024
  throw new Error('WORKSPACE_PLATFORM_UNSUPPORTED');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.139",
3
+ "version": "0.9.140",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  ],
14
14
  "bin": {
15
15
  "mar-node": "./dist/main.js",
16
- "marmgr": "./dist/marmgr.js",
16
+ "marctl": "./dist/marctl.js",
17
17
  "marnodes": "./dist/marnodes.js",
18
18
  "marssh": "./dist/marssh.js",
19
19
  "marscp": "./dist/marscp.js"
@@ -34,10 +34,10 @@
34
34
  "npm": "11.12.1",
35
35
  "ws": "^8.21.3",
36
36
  "zod": "4.4.3",
37
- "@myagentroam/marmgr": "0.9.119",
38
- "@myagentroam/agent": "0.9.139",
37
+ "@myagentroam/marctl": "0.9.120",
38
+ "@myagentroam/protocol": "0.9.140",
39
39
  "@myagentroam/code-navigation": "0.9.129",
40
- "@myagentroam/protocol": "0.9.139"
40
+ "@myagentroam/agent": "0.9.140"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/ws": "^8.18.1"