@myagentroam/node 0.9.155 → 0.9.156

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
@@ -601,6 +601,7 @@ export class NodeConnector {
601
601
  }
602
602
  }, {
603
603
  modelFactory: this.marAgentRuntimeModels,
604
+ retainEnvironment: (environment) => this.gitExecution().retain(environment),
604
605
  computerUse: this.computerUseProvider
605
606
  });
606
607
  this.marAgentClient.setSessionResourceReleaser((sessionId) => this.marAgentRunController.releaseSession(sessionId));
@@ -973,6 +974,7 @@ export class NodeConnector {
973
974
  projection: this.nativeSessionProjectionService,
974
975
  resolveForCommand: (sessionId) => this.externalSessionResumeService.resolveForMessage(sessionId),
975
976
  profiles: () => this.runnerService.profiles(),
977
+ mcps: (workspaceId) => this.database?.effectiveMcpInstallations(workspaceId) ?? [],
976
978
  present: (session) => this.sessionPresentationService.present(session),
977
979
  resumeQueueAfterCommandRun: (sessionId) => this.workspaceQueueWorkbenchService.resumeAfterCommandRun(sessionId),
978
980
  invalidateHistory: (sessionId) => this.nativeSessionWatchService.invalidate(sessionId),
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { mcpRuntimeDescriptorSchema } from '@myagentroam/protocol';
3
3
  import { nodeExecutionEnvironment } from '../node-execution-environment.js';
4
+ import { canonicalJson } from '../util/canonical-json.js';
4
5
  /**
5
6
  * Stable lifecycle boundary for every Node Runner implementation.
6
7
  *
@@ -238,7 +239,7 @@ function resolvedMcpServers(raw, secrets) {
238
239
  continue;
239
240
  const configuration = isRecord(record['configuration']) ? record['configuration'] : {};
240
241
  const sourceIdentity = createHash('sha256')
241
- .update(JSON.stringify({
242
+ .update(canonicalJson({
242
243
  id: record['id'] ?? null,
243
244
  name: record['serverName'],
244
245
  runtime: runtime.data,
@@ -9,6 +9,7 @@ import { LocalAgentHost, createMarAgent, generateSessionTitle } from '@myagentro
9
9
  import { marAgentReasoningEffortSchema, marAgentRuntimeModelConfigurationSchema } from '@myagentroam/protocol';
10
10
  import { boundedConversationItemId } from '../codex/conversation-parser.js';
11
11
  import { parseSecretEnvironment } from '../../util/secret-environment.js';
12
+ import { canonicalJson } from '../../util/canonical-json.js';
12
13
  import { MarAgentMcpClientCache } from './mcp-client-pool.js';
13
14
  import { MarAgentConversationNormalizer } from './conversation-normalizer.js';
14
15
  import { parseRunnerImageAttachments } from '../../util/node-operation-parsers.js';
@@ -29,6 +30,7 @@ export class MarAgentManagedRunController {
29
30
  #questions = new Map();
30
31
  #normalizers = new Map();
31
32
  #resourceIdleTtlMs;
33
+ #retainEnvironment;
32
34
  #createAgent;
33
35
  #modelFactory;
34
36
  #computerUse;
@@ -39,6 +41,7 @@ export class MarAgentManagedRunController {
39
41
  this.runner = runner;
40
42
  this.host = host;
41
43
  this.#resourceIdleTtlMs = options.resourceIdleTtlMs ?? 10 * 60_000;
44
+ this.#retainEnvironment = options.retainEnvironment;
42
45
  this.#createAgent = options.createAgent ?? createMarAgent;
43
46
  this.#modelFactory =
44
47
  options.modelFactory ??
@@ -139,7 +142,7 @@ export class MarAgentManagedRunController {
139
142
  mode: command.id,
140
143
  secretEnvironment: context.secretEnvironment,
141
144
  externalSessionId: session.externalSessionId,
142
- mcpServers: [],
145
+ mcpServers: this.#resolveMcpServers(context.mcpInstallations, context.secretEnvironment),
143
146
  images: [],
144
147
  ...(context.initiatedByUserId === undefined
145
148
  ? {}
@@ -173,13 +176,11 @@ export class MarAgentManagedRunController {
173
176
  try {
174
177
  secretEnvironment = parseSecretEnvironment(payload.secretEnvironment);
175
178
  const mcpSecretEnvironment = parseSecretEnvironment(payload.mcpSecretEnvironment);
176
- mcpServers = this.runner.mcpConfiguration(payload.mcpInstallations, {
179
+ mcpServers = this.#resolveMcpServers(payload.mcpInstallations, {
177
180
  ...secretEnvironment,
178
181
  ...mcpSecretEnvironment,
179
182
  MAR_USERNAME: secretEnvironment['MAR_USERNAME'] ?? ''
180
183
  });
181
- if (mcpServers.some((server) => server.name === 'computer'))
182
- throw new Error('MAR_AGENT_MCP_RESERVED_NAMESPACE');
183
184
  images = parseRunnerImageAttachments(payload.attachments);
184
185
  }
185
186
  catch {
@@ -566,7 +567,8 @@ export class MarAgentManagedRunController {
566
567
  throw error;
567
568
  }
568
569
  }
569
- const personalInstructions = input.personalInstructions ?? '';
570
+ const personalInstructions = input.personalInstructions ??
571
+ (input.mode === 'compact' ? existing.personalInstructions : '');
570
572
  if (existing.personalInstructions !== personalInstructions) {
571
573
  existing.agent.setSystemInstruction(personalInstructions || undefined);
572
574
  existing.personalInstructions = personalInstructions;
@@ -608,7 +610,9 @@ export class MarAgentManagedRunController {
608
610
  let agent;
609
611
  let runtime;
610
612
  let models = [];
613
+ let releaseEnvironment;
611
614
  try {
615
+ releaseEnvironment = this.#retainEnvironment?.(input.secretEnvironment ?? {});
612
616
  const mcp = mcpLease.pool;
613
617
  const localHost = new LocalAgentHost({
614
618
  mode: 'node',
@@ -646,6 +650,7 @@ export class MarAgentManagedRunController {
646
650
  host: localHost,
647
651
  normalizer: new MarAgentConversationNormalizer(),
648
652
  mcpLease,
653
+ releaseEnvironment,
649
654
  automaticTitleEnabled: input.externalSessionId === undefined,
650
655
  models,
651
656
  modelFingerprint: sessionModelFingerprint(input),
@@ -667,9 +672,16 @@ export class MarAgentManagedRunController {
667
672
  await agent?.dispose().catch(() => undefined);
668
673
  await closeModelCredentialProviders(models);
669
674
  await mcpLease.release().catch(() => undefined);
675
+ releaseEnvironment?.();
670
676
  throw error;
671
677
  }
672
678
  }
679
+ #resolveMcpServers(installations, environment) {
680
+ const servers = this.runner.mcpConfiguration(installations, environment);
681
+ if (servers.some((server) => server.name === 'computer'))
682
+ throw new Error('MAR_AGENT_MCP_RESERVED_NAMESPACE');
683
+ return servers;
684
+ }
673
685
  #runtimeInUse(runtime) {
674
686
  return (runtime.activeRunId !== undefined ||
675
687
  [...this.#runtimeAcquisitions.keys()].some((key) => this.#sessionRuntimes.get(key) === runtime));
@@ -737,6 +749,7 @@ export class MarAgentManagedRunController {
737
749
  }
738
750
  finally {
739
751
  await runtime.mcpLease.release().catch(() => undefined);
752
+ runtime.releaseEnvironment?.();
740
753
  for (const [sessionId, candidate] of this.#sessionRuntimes)
741
754
  if (candidate === runtime)
742
755
  this.#sessionRuntimes.delete(sessionId);
@@ -837,7 +850,7 @@ function isExecutionScopedMcp(server) {
837
850
  }
838
851
  function sessionHostFingerprint(input, hostConfiguration) {
839
852
  return createHash('sha256')
840
- .update(JSON.stringify({
853
+ .update(canonicalJson({
841
854
  cwd: input.cwd,
842
855
  mcpServers: input.mcpServers.filter((server) => !isExecutionScopedMcp(server)),
843
856
  environment: Object.entries(hostConfiguration.environment)
@@ -4,6 +4,7 @@ import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotoc
4
4
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
5
5
  import { ToolListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
6
6
  import { assertMcpToolDefinition, assertMcpToolCatalog, MarAgentError } from '@myagentroam/agent';
7
+ import { canonicalJson } from '../../util/canonical-json.js';
7
8
  import { ExecutorStdioClientTransport } from './executor-stdio-client-transport.js';
8
9
  export class MarAgentMcpClientCache {
9
10
  #entries = new Map();
@@ -444,9 +445,7 @@ export class MarAgentMcpClientPool {
444
445
  }
445
446
  }
446
447
  function fingerprint(value) {
447
- return createHash('sha256')
448
- .update(JSON.stringify(canonicalValue(value)))
449
- .digest('hex');
448
+ return createHash('sha256').update(canonicalJson(value)).digest('hex');
450
449
  }
451
450
  function singleUseLease(pool) {
452
451
  let released = false;
@@ -462,23 +461,14 @@ function singleUseLease(pool) {
462
461
  }
463
462
  function mcpConfigurationFingerprint(servers, cwd, authorizationScope) {
464
463
  return createHash('sha256')
465
- .update(JSON.stringify(canonicalValue({
464
+ .update(canonicalJson({
466
465
  cwd,
467
466
  authorizationScope,
468
467
  inheritedEnvironment: getDefaultEnvironment(),
469
468
  servers
470
- })))
469
+ }))
471
470
  .digest('hex');
472
471
  }
473
- function canonicalValue(value) {
474
- if (Array.isArray(value))
475
- return value.map(canonicalValue);
476
- if (value === null || typeof value !== 'object')
477
- return value;
478
- return Object.fromEntries(Object.entries(value)
479
- .sort(([left], [right]) => left.localeCompare(right))
480
- .map(([key, entry]) => [key, canonicalValue(entry)]));
481
- }
482
472
  function safeName(value) {
483
473
  return value.replace(/[^a-zA-Z0-9_-]/gu, '_').slice(0, 64);
484
474
  }
@@ -31,11 +31,13 @@ export class GitExecutionService {
31
31
  const parsed = personalGitRuntimeSchema.safeParse(configuration);
32
32
  if (!parsed.success)
33
33
  throw new Error('GIT_CONFIGURATION_INVALID');
34
+ // Execution credentials rotate independently of the user's stable Git capability.
35
+ const stableEnvironment = Object.fromEntries(Object.entries(baseEnvironment).filter(([name]) => name !== 'MAR_CTL_USER_TOKEN'));
34
36
  const key = createHash('sha256')
35
37
  .update(JSON.stringify([
36
38
  userId,
37
39
  parsed.data,
38
- Object.entries(baseEnvironment).sort(([a], [b]) => a.localeCompare(b))
40
+ Object.entries(stableEnvironment).sort(([a], [b]) => a.localeCompare(b))
39
41
  ]))
40
42
  .digest('hex');
41
43
  const endpoint = await this.start();
@@ -44,10 +46,10 @@ export class GitExecutionService {
44
46
  let context = this.contexts.get(key);
45
47
  if (context === undefined) {
46
48
  const token = randomBytes(32).toString('hex');
47
- const original = { ...process.env, ...baseEnvironment };
49
+ const original = { ...process.env, ...stableEnvironment };
48
50
  const binary = await gitExecutable(original);
49
51
  const environment = {
50
- ...baseEnvironment,
52
+ ...stableEnvironment,
51
53
  MAR_GIT_ENDPOINT: endpoint,
52
54
  MAR_GIT_TOKEN: token
53
55
  };
@@ -77,9 +79,12 @@ export class GitExecutionService {
77
79
  environment[pathKey] =
78
80
  `${await this.gitWrapper(binary)}${delimiter}${original[pathKey] ?? ''}`;
79
81
  const restore = Object.fromEntries(Object.keys(environment)
80
- .filter((name) => environment[name] !== baseEnvironment[name] || name.startsWith('MAR_GIT_'))
82
+ .filter((name) => environment[name] !== stableEnvironment[name] || name.startsWith('MAR_GIT_'))
81
83
  .map((name) => [name, name.startsWith('MAR_GIT_') ? null : (original[name] ?? null)]));
82
- context = {
84
+ if (this.closed)
85
+ throw new Error('GIT_EXECUTION_CLOSED');
86
+ // Binary discovery and wrapper creation yield; another acquire may have published it.
87
+ context = this.contexts.get(key) ?? {
83
88
  key,
84
89
  token,
85
90
  configuration: parsed.data,
@@ -89,9 +94,12 @@ export class GitExecutionService {
89
94
  references: 0
90
95
  };
91
96
  this.contexts.set(key, context);
92
- this.tokens.set(token, context);
97
+ this.tokens.set(context.token, context);
93
98
  }
94
- return { environment: context.environment, release: this.reference(context) };
99
+ return {
100
+ environment: Object.freeze({ ...baseEnvironment, ...context.environment }),
101
+ release: this.reference(context)
102
+ };
95
103
  }
96
104
  retain(environment) {
97
105
  if (environment.MAR_GIT_TOKEN === undefined)
@@ -112,7 +120,7 @@ export class GitExecutionService {
112
120
  released = true;
113
121
  if (--context.references !== 0 || this.closed)
114
122
  return;
115
- // This outlives the existing 60-second warm Runner window.
123
+ // Warm runtimes retain their own reference; this grace period starts after the last owner.
116
124
  context.timer = setTimeout(() => this.retire(context), this.options.idleTimeoutMs ?? 70_000);
117
125
  context.timer.unref();
118
126
  };
@@ -106,6 +106,7 @@ export class SessionCommandService {
106
106
  }
107
107
  const result = await runner.executeCommand(session, command, invocation.input, {
108
108
  secretEnvironment,
109
+ mcpInstallations: this.options.mcps(session.workspaceId),
109
110
  ...(input.marAgentModel === undefined ? {} : { marAgentModel: input.marAgentModel }),
110
111
  ...(input.marAgentModels === undefined ? {} : { marAgentModels: input.marAgentModels }),
111
112
  ...(input.marAgentRuntimeScopeId === undefined
@@ -0,0 +1,13 @@
1
+ /** Canonicalizes JSON dictionary keys without changing array order or values. */
2
+ export function canonicalJson(value) {
3
+ return JSON.stringify(canonicalValue(value));
4
+ }
5
+ function canonicalValue(value) {
6
+ if (Array.isArray(value))
7
+ return value.map(canonicalValue);
8
+ if (value === null || typeof value !== 'object')
9
+ return value;
10
+ return Object.fromEntries(Object.entries(value)
11
+ .sort(([left], [right]) => left.localeCompare(right))
12
+ .map(([key, entry]) => [key, canonicalValue(entry)]));
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.155",
3
+ "version": "0.9.156",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -36,10 +36,10 @@
36
36
  "playwright": "1.62.1",
37
37
  "ws": "^8.21.3",
38
38
  "zod": "4.4.3",
39
- "@myagentroam/agent": "0.9.155",
40
- "@myagentroam/code-navigation": "0.9.129",
39
+ "@myagentroam/agent": "0.9.156",
41
40
  "@myagentroam/marctl": "0.9.120",
42
- "@myagentroam/protocol": "0.9.155"
41
+ "@myagentroam/code-navigation": "0.9.129",
42
+ "@myagentroam/protocol": "0.9.156"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/ws": "^8.18.1"