@myagentroam/node 0.10.60 → 0.10.62

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.
@@ -1,18 +1,13 @@
1
1
  import { NODE_MCP } from '../../mcp/naming.js';
2
2
  import { computerUseHostSkill } from './computer-use-skill.js';
3
3
  import { marctlExecutionEnvironment, marctlHostSkill } from './marctl-skill.js';
4
- import { compactRuntimeConfigurations, marAgentCompactExtensionSource, updateMarAgentCompactState } from './compact-extensions.js';
5
4
  import { createHash, randomUUID } from 'node:crypto';
6
5
  import { homedir } from 'node:os';
7
- import { access, constants } from 'node:fs/promises';
8
- import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
9
6
  import { nodeLog } from '../../operational.js';
10
- import { BUNDLED_RIPGREP_VERSION, bundledRipgrepPath, prependBundledRipgrepToPath } from '../../ripgrep-binary.js';
11
- import { LocalAgentHost, createMarAgent, generateSessionTitle } from '@myagentroam/agent';
12
- import { marAgentReasoningEffortSchema, resolveMarAgentDefaultReasoningEffort, marAgentRuntimeModelConfigurationSchema, marAgentRuntimeCompactConfigurationSchema } from '@myagentroam/protocol';
7
+ import { createMarAgent, generateSessionTitle } from '@myagentroam/agent';
8
+ import { marAgentReasoningEffortSchema, resolveMarAgentDefaultReasoningEffort, marAgentRuntimeModelConfigurationSchema } from '@myagentroam/protocol';
13
9
  import { boundedConversationItemId } from '../codex/conversation-parser.js';
14
10
  import { parseSecretEnvironment } from '../../util/secret-environment.js';
15
- import { canonicalJson } from '../../util/canonical-json.js';
16
11
  import { MarAgentMcpClientCache } from './mcp-client-pool.js';
17
12
  import { MarAgentConversationNormalizer, normalizeMarAgentUsage } from './conversation-normalizer.js';
18
13
  import { parseRunnerImageAttachments } from '../../util/node-operation-parsers.js';
@@ -21,29 +16,18 @@ import { MarAgentRuntimeModelFactory, sdkModelConfigurations, closeImageCredenti
21
16
  import { createImageGenerationExtensions } from './image-generation-extensions.js';
22
17
  import { createWebToolsExtensions } from './web-tools-extensions.js';
23
18
  import { combineExecutionExtensions } from './execution-extensions.js';
24
- const HOST_CONFIGURATION_CATEGORIES = [
25
- 'cwd',
26
- 'mcpServers',
27
- 'environment',
28
- 'guaranteedCommands'
29
- ];
19
+ import { MarAgentSessionRuntimeRegistry } from './session-runtime-registry.js';
20
+ import { isExecutionScopedMcp } from './session-runtime.js';
30
21
  export class MarAgentManagedRunController {
31
22
  runner;
32
23
  host;
33
24
  #mcpClients = new MarAgentMcpClientCache();
34
25
  #executions = new Map();
35
26
  #steerTargets = new Map();
36
- #sessionRuntimes = new Map();
37
- #runtimeAcquisitions = new Map();
38
- #runtimeCreations = new Map();
39
- #sessionReleases = new Map();
27
+ #runtimes;
40
28
  #questions = new Map();
41
29
  #approvals = new Map();
42
30
  #normalizers = new Map();
43
- #resourceIdleTtlMs;
44
- #retainEnvironment;
45
- #createAgent;
46
- #modelFactory;
47
31
  #computerUse;
48
32
  #generateTitle;
49
33
  #disposePromise;
@@ -51,16 +35,30 @@ export class MarAgentManagedRunController {
51
35
  constructor(runner, host, options = {}) {
52
36
  this.runner = runner;
53
37
  this.host = host;
54
- this.#resourceIdleTtlMs = options.resourceIdleTtlMs ?? 10 * 60_000;
55
- this.#retainEnvironment = options.retainEnvironment;
56
- this.#createAgent = options.createAgent ?? createMarAgent;
57
- this.#modelFactory =
58
- options.modelFactory ??
38
+ this.#runtimes = new MarAgentSessionRuntimeRegistry({
39
+ mcpClients: this.#mcpClients,
40
+ resourceIdleTtlMs: options.resourceIdleTtlMs ?? 10 * 60_000,
41
+ ...(options.retainEnvironment === undefined
42
+ ? {}
43
+ : { retainEnvironment: options.retainEnvironment }),
44
+ createAgent: options.createAgent ?? createMarAgent,
45
+ modelFactory: options.modelFactory ??
59
46
  new MarAgentRuntimeModelFactory({
60
47
  resolve: async () => {
61
48
  throw new Error('MAR_AGENT_RUNTIME_CREDENTIALS_UNSUPPORTED');
62
49
  }
63
- });
50
+ }),
51
+ customTitle: (sessionId) => this.host.customTitle?.(sessionId) ?? undefined,
52
+ modelUsage: (runId, usage) => this.host.emit(runId, 'run.usage', normalizeMarAgentUsage(usage)),
53
+ question: (runId, request, signal) => this.requestQuestion(runId, request, signal),
54
+ ...(this.host.createApproval && this.host.publishApproval
55
+ ? {
56
+ approval: (runId, request, signal) => this.requestApproval(runId, request, signal)
57
+ }
58
+ : {})
59
+ }, (sessionId, nativeId) => this.host.promote(sessionId, nativeId), async (sessionId) => {
60
+ await this.#computerUse?.releaseSession(sessionId);
61
+ });
64
62
  this.#computerUse = options.computerUse;
65
63
  this.#generateTitle = options.generateTitle ?? generateSessionTitle;
66
64
  }
@@ -68,50 +66,25 @@ export class MarAgentManagedRunController {
68
66
  if (this.#disposePromise !== undefined)
69
67
  return this.#disposePromise;
70
68
  this.#disposed = true;
71
- this.#disposePromise = (async () => {
72
- for (const execution of this.#executions.values()) {
73
- execution.cancelTitle();
74
- execution.cancel();
75
- }
76
- await Promise.allSettled([...new Set(this.#runtimeAcquisitions.values())]);
69
+ this.#disposePromise = Promise.resolve().then(async () => {
77
70
  for (const execution of this.#executions.values()) {
78
71
  execution.cancelTitle();
79
72
  execution.cancel();
80
73
  }
81
- await Promise.all([...new Set(this.#sessionRuntimes.values())].map((runtime) => this.#releaseRuntime(runtime, 'controller_disposed')));
82
- await this.#mcpClients.close();
83
- await this.#computerUse?.dispose();
84
- })();
74
+ const results = await Promise.allSettled([
75
+ this.#runtimes.dispose(),
76
+ ...[...this.#executions.values()].map((execution) => execution.settled)
77
+ ]);
78
+ results.push(...(await Promise.allSettled([this.#mcpClients.close()])));
79
+ results.push(...(await Promise.allSettled([this.#computerUse?.dispose()])));
80
+ const failure = results.find((result) => result.status === 'rejected');
81
+ if (failure?.status === 'rejected')
82
+ throw failure.reason;
83
+ });
85
84
  return this.#disposePromise;
86
85
  }
87
86
  releaseSession(sessionId) {
88
- const existing = this.#sessionReleases.get(sessionId);
89
- if (existing !== undefined)
90
- return existing;
91
- const release = (async () => {
92
- await this.#runtimeAcquisitions.get(sessionId)?.catch(() => undefined);
93
- const runtime = this.#sessionRuntimes.get(sessionId);
94
- if (runtime === undefined) {
95
- await this.#computerUse?.releaseSession(sessionId);
96
- return;
97
- }
98
- if (runtime.activeRunId !== undefined) {
99
- const execution = this.#executions.get(runtime.activeRunId);
100
- execution?.cancelTitle();
101
- execution?.cancel();
102
- }
103
- await this.#releaseRuntime(runtime, 'session_released');
104
- await Promise.all([
105
- this.#computerUse?.releaseSession(sessionId),
106
- this.#computerUse?.releaseSession(runtime.session.id)
107
- ]);
108
- })();
109
- const tracked = release.finally(() => {
110
- if (this.#sessionReleases.get(sessionId) === tracked)
111
- this.#sessionReleases.delete(sessionId);
112
- });
113
- this.#sessionReleases.set(sessionId, tracked);
114
- return tracked;
87
+ return this.#runtimes.releaseSession(sessionId);
115
88
  }
116
89
  async executeCommand(session, command, input, context) {
117
90
  if (command.id === 'plan') {
@@ -260,28 +233,24 @@ export class MarAgentManagedRunController {
260
233
  const titleController = new AbortController();
261
234
  let cancelExecution;
262
235
  // Own cancellation before any asynchronous preparation, not only after SDK admission.
263
- this.#executions.set(input.runId, {
236
+ let resolveSettled;
237
+ const settled = new Promise((resolve) => {
238
+ resolveSettled = resolve;
239
+ });
240
+ const execution = {
264
241
  cancel: () => {
265
242
  if (controller.signal.aborted)
266
243
  return;
267
244
  controller.abort();
268
245
  cancelExecution?.();
269
246
  },
270
- cancelTitle: () => titleController.abort()
271
- });
272
- this.host.active.add(input.runId);
273
- this.host.adopt({
274
- runId: input.runId,
275
- sessionId: input.sessionId,
276
- runner: 'mar-agent',
277
- cwd: input.cwd,
278
- ...(input.externalSessionId ? { externalSessionId: input.externalSessionId } : {}),
279
- ...(input.initiatedByUserId === undefined
280
- ? {}
281
- : { initiatedByUserId: input.initiatedByUserId })
282
- });
283
- this.host.emit(input.runId, 'run.started', {}, 'STARTING');
247
+ cancelTitle: () => titleController.abort(),
248
+ settled
249
+ };
250
+ this.#executions.set(input.runId, execution);
284
251
  let runtime;
252
+ let use;
253
+ let terminalPublished = false;
285
254
  let executionMcpLease;
286
255
  let executionMcpBinding;
287
256
  let computerUseBinding;
@@ -289,42 +258,35 @@ export class MarAgentManagedRunController {
289
258
  let titleTask;
290
259
  let cancelledDuringPreparation = false;
291
260
  try {
261
+ this.host.active.add(input.runId);
262
+ this.host.adopt({
263
+ runId: input.runId,
264
+ sessionId: input.sessionId,
265
+ runner: 'mar-agent',
266
+ cwd: input.cwd,
267
+ ...(input.externalSessionId ? { externalSessionId: input.externalSessionId } : {}),
268
+ ...(input.initiatedByUserId === undefined
269
+ ? {}
270
+ : { initiatedByUserId: input.initiatedByUserId })
271
+ });
272
+ this.host.emit(input.runId, 'run.started', {}, 'STARTING');
273
+ if (this.#disposed)
274
+ throw new Error('MAR_AGENT_RUN_CONTROLLER_DISPOSED');
292
275
  controller.signal.throwIfAborted();
293
- const sessionKey = input.externalSessionId ?? input.sessionId;
294
- // Reserve both identities while opening so a concurrent release can await the same runtime.
295
- const acquisitionKeys = [...new Set([sessionKey, input.sessionId])];
296
- if (acquisitionKeys.some((key) => this.#runtimeAcquisitions.get(key) !== undefined))
297
- throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
298
- const acquisition = this.#acquireRuntime(input);
299
- for (const key of acquisitionKeys)
300
- this.#runtimeAcquisitions.set(key, acquisition);
301
- let acquired = false;
302
- try {
303
- runtime = await acquisition;
304
- acquired = true;
305
- executionModels = runtime.models;
306
- }
307
- finally {
308
- for (const key of acquisitionKeys)
309
- if (this.#runtimeAcquisitions.get(key) === acquisition)
310
- this.#runtimeAcquisitions.delete(key);
311
- // A failed acquisition may have retained an idle Runtime before setup failed.
312
- if (!acquired)
313
- this.#restoreRuntimeRelease(this.#sessionRuntimes.get(sessionKey));
314
- }
315
- controller.signal.throwIfAborted();
316
- if (this.#disposed ||
317
- this.#sessionReleases.has(sessionKey) ||
318
- this.#sessionReleases.has(input.sessionId) ||
319
- this.#sessionReleases.has(runtime.session.id)) {
320
- await this.#releaseRuntime(runtime, 'admission_released');
321
- throw new Error('MAR_AGENT_SESSION_RUNTIME_RELEASED');
322
- }
323
- if (runtime.activeRunId !== undefined)
324
- throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
325
- runtime.activeRunId = input.runId;
276
+ use = await this.#runtimes.acquire(input, {
277
+ runId: input.runId,
278
+ signal: controller.signal,
279
+ cancel: () => {
280
+ titleController.abort();
281
+ execution.cancel();
282
+ },
283
+ settled
284
+ });
285
+ runtime = use.runtime;
286
+ executionModels = runtime.models;
287
+ use.assertUsable();
326
288
  await runtime.mcpLease.pool.refreshForExecution();
327
- controller.signal.throwIfAborted();
289
+ use.assertUsable();
328
290
  const marctlSkill = marctlHostSkill(input.secretEnvironment);
329
291
  const executionMcpServers = input.mcpServers
330
292
  .filter(isExecutionScopedMcp)
@@ -337,23 +299,14 @@ export class MarAgentManagedRunController {
337
299
  enabled: input.computerUseEnabled === true,
338
300
  modelSupportsImages: input.model.inputCapabilities.includes('IMAGE')
339
301
  });
340
- controller.signal.throwIfAborted();
341
- if (this.#disposed ||
342
- this.#sessionReleases.has(sessionKey) ||
343
- this.#sessionReleases.has(input.sessionId) ||
344
- this.#sessionReleases.has(runtime.session.id)) {
345
- await computerUseBinding?.close().catch(() => undefined);
346
- computerUseBinding = undefined;
347
- await this.#computerUse?.releaseSession(runtime.session.id);
348
- throw new Error('MAR_AGENT_SESSION_RUNTIME_RELEASED');
349
- }
302
+ use.assertUsable();
350
303
  if (executionMcpServers.length > 0) {
351
304
  executionMcpLease = await this.#mcpClients.acquire(executionMcpServers, input.cwd, {
352
305
  reusable: false,
353
306
  authorizationScope: runtime.session.id
354
307
  });
355
308
  }
356
- controller.signal.throwIfAborted();
309
+ use.assertUsable();
357
310
  const userTools = executionMcpLease?.pool.definitions() ?? [];
358
311
  const nodeTools = computerUseBinding?.tools ?? [];
359
312
  if (userTools.some((tool) => tool.name.startsWith('mcp__mar_computer__')))
@@ -441,7 +394,7 @@ export class MarAgentManagedRunController {
441
394
  const metadata = input.mode === 'compact' || !runtime.automaticTitleEnabled
442
395
  ? undefined
443
396
  : await runtime.agent.getSession(session.id).catch(() => undefined);
444
- controller.signal.throwIfAborted();
397
+ use.assertUsable();
445
398
  const handle = input.mode === 'plan'
446
399
  ? session.plan({
447
400
  ...runInput
@@ -498,7 +451,6 @@ export class MarAgentManagedRunController {
498
451
  });
499
452
  return undefined;
500
453
  });
501
- runtime.automaticTitleTask = { cancel: () => titleController.abort(), result: titleTask };
502
454
  }
503
455
  this.project(input.runId, event);
504
456
  }
@@ -528,7 +480,8 @@ export class MarAgentManagedRunController {
528
480
  : result.status === 'cancelled'
529
481
  ? 'CANCELLED'
530
482
  : 'FAILED';
531
- this.#prepareRuntimeForTerminal(input.runId, runtime);
483
+ use?.terminal();
484
+ terminalPublished = true;
532
485
  this.host.emit(input.runId, status === 'SUCCEEDED'
533
486
  ? 'run.completed'
534
487
  : status === 'CANCELLED'
@@ -551,13 +504,18 @@ export class MarAgentManagedRunController {
551
504
  }, status);
552
505
  }
553
506
  catch (error) {
554
- if (controller.signal.aborted && cancelExecution === undefined) {
507
+ if (terminalPublished) {
508
+ nodeLog('runner.mar-agent.run.cleanup_failed', {
509
+ runId: input.runId,
510
+ code: 'MAR_AGENT_RUN_CLEANUP_FAILED'
511
+ });
512
+ }
513
+ else if (controller.signal.aborted && cancelExecution === undefined) {
555
514
  // Keep the Run owned until preparation and its resources actually settle.
556
515
  cancelledDuringPreparation = true;
557
516
  }
558
517
  else {
559
- if (runtime !== undefined)
560
- this.#prepareRuntimeForTerminal(input.runId, runtime);
518
+ use?.terminal();
561
519
  const projected = runnerErrorProjection(error, 'MAR_AGENT_RUN_FAILED');
562
520
  this.host.emitItem(input.runId, {
563
521
  itemId: boundedConversationItemId('mar-agent-error', input.runId),
@@ -574,296 +532,55 @@ export class MarAgentManagedRunController {
574
532
  }
575
533
  finally {
576
534
  titleController.abort();
577
- await titleTask;
578
- if (runtime !== undefined && runtime.automaticTitleTask?.result === titleTask)
579
- delete runtime.automaticTitleTask;
580
- this.clearInteractions(input.runId);
581
- this.#normalizers.delete(input.runId);
582
- await closeImageCredentialProviders(executionModels);
583
- await computerUseBinding?.close().catch(() => undefined);
584
- runtime?.host.unbindExecutionMcp(executionMcpBinding);
585
- await executionMcpLease?.release().catch(() => undefined);
586
- if (runtime !== undefined)
587
- await this.#finishRuntimeExecution(input.runId, runtime).catch(() => undefined);
588
- this.host.active.delete(input.runId);
589
- this.#executions.delete(input.runId);
590
- this.#steerTargets.delete(input.runId);
591
- if (cancelledDuringPreparation)
592
- this.host.emit(input.runId, 'run.cancelled', { status: 'CANCELLED' }, 'CANCELLED');
593
- }
594
- }
595
- #prepareRuntimeForTerminal(runId, runtime) {
596
- if (runtime.activeRunId !== runId)
597
- return;
598
- delete runtime.activeRunId;
599
- this.#scheduleRuntimeRelease(runtime);
600
- }
601
- async #finishRuntimeExecution(runId, runtime) {
602
- if (runtime.activeRunId === runId)
603
- this.#prepareRuntimeForTerminal(runId, runtime);
604
- else
605
- this.#restoreRuntimeRelease(runtime);
606
- }
607
- async #acquireRuntime(input) {
608
- if (this.#disposed)
609
- throw new Error('MAR_AGENT_RUN_CONTROLLER_DISPOSED');
610
- const sessionKey = input.externalSessionId ?? input.sessionId;
611
- const retained = this.#retainRuntime(sessionKey);
612
- try {
613
- const hostConfiguration = await resolveMarAgentHostRuntimeConfiguration(input.secretEnvironment);
614
- if (this.#disposed)
615
- throw new Error('MAR_AGENT_RUN_CONTROLLER_DISPOSED');
616
- const fingerprint = sessionHostFingerprint(input, hostConfiguration);
617
- const modelFingerprint = sessionModelFingerprint(input);
618
- const creationKeys = [...new Set([sessionKey, input.sessionId])];
619
- const pending = creationKeys
620
- .map((key) => this.#runtimeCreations.get(key))
621
- .find((creation) => creation !== undefined);
622
- if (pending !== undefined) {
623
- await pending;
624
- return this.#acquireRuntime(input);
625
- }
626
- const existing = this.#sessionRuntimes.get(sessionKey);
627
- if (existing !== undefined) {
628
- if (existing.activeRunId !== undefined)
629
- throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
630
- if (!existing.released &&
631
- existing.release === undefined &&
632
- existing.fingerprint.value === fingerprint.value) {
633
- const configurations = compactRuntimeConfigurations(input.models, input.compact);
634
- if (existing.modelFingerprint !== modelFingerprint ||
635
- configurations.some((model) => model.credential?.type === 'RUNTIME_TICKET' ||
636
- model.imageGeneration?.credential?.type === 'RUNTIME_TICKET')) {
637
- const models = this.#modelFactory.create(configurations, {
638
- ...(input.runtimeScopeId === undefined
639
- ? {}
640
- : { runtimeScopeId: input.runtimeScopeId }),
641
- executionId: input.runId
642
- });
643
- try {
644
- await existing.agent.reconfigureModels({
645
- models: sdkModelConfigurations(models.filter((model) => input.models.some((candidate) => candidate.id === model.id))),
646
- defaultModelId: input.model.id
647
- });
648
- existing.models = models;
649
- existing.modelFingerprint = modelFingerprint;
650
- }
651
- catch (error) {
652
- await closeModelCredentialProviders(models);
653
- throw error;
654
- }
535
+ const cleanup = [
536
+ async () => {
537
+ await titleTask;
538
+ },
539
+ async () => {
540
+ this.clearInteractions(input.runId);
541
+ },
542
+ async () => {
543
+ await closeImageCredentialProviders(executionModels);
544
+ },
545
+ async () => {
546
+ await computerUseBinding?.close();
547
+ },
548
+ async () => {
549
+ runtime?.host.unbindExecutionMcp(executionMcpBinding);
550
+ },
551
+ async () => {
552
+ await executionMcpLease?.release();
553
+ }
554
+ ];
555
+ try {
556
+ for (const close of cleanup) {
557
+ try {
558
+ await close();
655
559
  }
656
- const personalInstructions = input.personalInstructions ??
657
- (input.mode === 'compact' ? existing.personalInstructions : '');
658
- if (existing.personalInstructions !== personalInstructions) {
659
- existing.agent.setSystemInstruction(personalInstructions || undefined);
660
- existing.personalInstructions = personalInstructions;
560
+ catch {
561
+ nodeLog('runner.mar-agent.run.cleanup_failed', {
562
+ runId: input.runId,
563
+ code: 'MAR_AGENT_RUN_CLEANUP_FAILED'
564
+ });
661
565
  }
662
- updateMarAgentCompactState(existing.compactState, input.compact, existing.models, input.models.map((model) => model.id));
663
- this.#retainRuntime(sessionKey);
664
- nodeLog('runner.mar-agent.runtime.reused', {
665
- runtimeId: existing.runtimeId,
666
- sessionId: existing.session.id,
667
- runId: input.runId
668
- });
669
- return existing;
670
566
  }
671
- await this.#releaseRuntime(existing, 'host_configuration_changed', HOST_CONFIGURATION_CATEGORIES.filter((category) => existing.fingerprint.categories[category] !== fingerprint.categories[category]));
672
- }
673
- const creation = this.#createRuntime(input, sessionKey, fingerprint, hostConfiguration);
674
- for (const key of creationKeys)
675
- this.#runtimeCreations.set(key, creation);
676
- try {
677
- return await creation;
678
567
  }
679
568
  finally {
680
- for (const key of creationKeys)
681
- if (this.#runtimeCreations.get(key) === creation)
682
- this.#runtimeCreations.delete(key);
569
+ use?.terminal();
570
+ this.#normalizers.delete(input.runId);
571
+ this.host.active.delete(input.runId);
572
+ this.#executions.delete(input.runId);
573
+ this.#steerTargets.delete(input.runId);
574
+ resolveSettled();
683
575
  }
684
- }
685
- catch (error) {
686
- this.#restoreRuntimeRelease(retained);
687
- throw error;
688
- }
689
- }
690
- async #createRuntime(input, sessionKey, fingerprint, hostConfiguration) {
691
- const sharedMcpServers = input.mcpServers.filter((server) => !isExecutionScopedMcp(server));
692
- const mcpLease = await this.#mcpClients.acquire(sharedMcpServers, input.cwd, {
693
- // Native Session IDs are assigned after the first lease; do not let that promotion
694
- // change a confirmed actor's discovery identity when its warm runtime is recreated.
695
- authorizationScope: input.secretEnvironment?.['MAR_USERNAME'] ? input.cwd : sessionKey
696
- });
697
- let agent;
698
- let runtime;
699
- let models = [];
700
- let releaseEnvironment;
701
- try {
702
- releaseEnvironment = this.#retainEnvironment?.(input.secretEnvironment ?? {});
703
- const mcp = mcpLease.pool;
704
- const localHost = new LocalAgentHost({
705
- modelUsage: (usage) => {
706
- const runId = runtime?.activeRunId;
707
- if (runId !== undefined)
708
- this.host.emit(runId, 'run.usage', normalizeMarAgentUsage(usage));
709
- },
710
- mode: 'node',
711
- homeDirectory: homedir(),
712
- workspace: input.cwd,
713
- environment: hostConfiguration.environment,
714
- guaranteedCommands: hostConfiguration.guaranteedCommands,
715
- question: async (request, signal) => {
716
- const runId = runtime?.activeRunId;
717
- return runId === undefined ? {} : this.requestQuestion(runId, request, signal);
718
- },
719
- approval: this.host.createApproval && this.host.publishApproval
720
- ? (request, signal) => {
721
- const runId = runtime?.activeRunId;
722
- if (runId === undefined)
723
- return Promise.resolve('REJECTED');
724
- return this.requestApproval(runId, request, signal);
725
- }
726
- : undefined,
727
- mcpCatalog: () => mcp.catalog(),
728
- callMcp: async (call, signal, context) => mcp.call(call.name, call.arguments, signal, context?.expectedTool),
729
- resourceIdleTtlMs: this.#resourceIdleTtlMs
730
- });
731
- models = this.#modelFactory.create(compactRuntimeConfigurations(input.models, input.compact), {
732
- ...(input.runtimeScopeId === undefined ? {} : { runtimeScopeId: input.runtimeScopeId }),
733
- executionId: input.runId
734
- });
735
- const compactState = { revision: 0 };
736
- updateMarAgentCompactState(compactState, input.compact, models, input.models.map((model) => model.id));
737
- agent = await this.#createAgent({
738
- mode: 'node',
739
- host: localHost,
740
- models: sdkModelConfigurations(models.filter((model) => input.models.some((candidate) => candidate.id === model.id))),
741
- defaultModelId: input.model.id,
742
- runtimeExtensions: marAgentCompactExtensionSource(compactState),
743
- ...(input.personalInstructions ? { systemInstruction: input.personalInstructions } : {})
744
- });
745
- const customTitle = this.host.customTitle?.(input.sessionId) ?? undefined;
746
- const session = input.externalSessionId
747
- ? await agent.openSession(input.externalSessionId)
748
- : await agent.createSession({
749
- workspace: input.cwd,
750
- ...(customTitle === undefined ? {} : { title: customTitle })
751
- });
752
- runtime = {
753
- runtimeId: randomUUID(),
754
- fingerprint,
755
- agent,
756
- session,
757
- host: localHost,
758
- normalizer: new MarAgentConversationNormalizer(),
759
- mcpLease,
760
- releaseEnvironment,
761
- automaticTitleEnabled: input.externalSessionId === undefined,
762
- models,
763
- compactState,
764
- modelFingerprint: sessionModelFingerprint(input),
765
- personalInstructions: input.personalInstructions ?? '',
766
- released: false
767
- };
768
- this.#sessionRuntimes.set(sessionKey, runtime);
769
- this.#sessionRuntimes.set(session.id, runtime);
770
- nodeLog('runner.mar-agent.runtime.created', {
771
- runtimeId: runtime.runtimeId,
772
- sessionId: session.id,
773
- runId: input.runId
774
- });
775
- if (!input.externalSessionId)
776
- this.host.promote(input.sessionId, session.id);
777
- return runtime;
778
- }
779
- catch (error) {
780
- await agent?.dispose().catch(() => undefined);
781
- await closeModelCredentialProviders(models);
782
- await mcpLease.release().catch(() => undefined);
783
- releaseEnvironment?.();
784
- throw error;
576
+ if (cancelledDuringPreparation)
577
+ this.host.emit(input.runId, 'run.cancelled', { status: 'CANCELLED' }, 'CANCELLED');
785
578
  }
786
579
  }
787
580
  #resolveMcpServers(installations, environment, executionServers = []) {
788
581
  const servers = this.runner.mcpConfiguration(installations, environment, executionServers);
789
582
  return servers;
790
583
  }
791
- #runtimeInUse(runtime) {
792
- return (runtime.activeRunId !== undefined ||
793
- [...this.#runtimeAcquisitions.keys()].some((key) => this.#sessionRuntimes.get(key) === runtime));
794
- }
795
- #scheduleRuntimeRelease(runtime) {
796
- // Old finalizers must neither reclaim a continuation nor extend its idle lease.
797
- if (runtime.released ||
798
- runtime.release !== undefined ||
799
- runtime.idleTimer !== undefined ||
800
- this.#runtimeInUse(runtime))
801
- return;
802
- const timer = setTimeout(() => {
803
- if (runtime.idleTimer !== timer)
804
- return;
805
- delete runtime.idleTimer;
806
- if (!this.#runtimeInUse(runtime))
807
- void this.#releaseRuntime(runtime, 'idle_expired');
808
- }, this.#resourceIdleTtlMs);
809
- runtime.idleTimer = timer;
810
- timer.unref();
811
- }
812
- #retainRuntime(sessionKey) {
813
- const runtime = this.#sessionRuntimes.get(sessionKey);
814
- if (runtime?.idleTimer !== undefined) {
815
- clearTimeout(runtime.idleTimer);
816
- delete runtime.idleTimer;
817
- }
818
- return runtime;
819
- }
820
- #restoreRuntimeRelease(runtime) {
821
- if (runtime === undefined ||
822
- runtime.released ||
823
- runtime.release !== undefined ||
824
- runtime.activeRunId !== undefined ||
825
- ![...this.#sessionRuntimes.values()].includes(runtime))
826
- return;
827
- this.#scheduleRuntimeRelease(runtime);
828
- }
829
- #releaseRuntime(runtime, reason, changedCategories = []) {
830
- if (runtime.release !== undefined)
831
- return runtime.release;
832
- runtime.release = (async () => {
833
- runtime.released = true;
834
- nodeLog('runner.mar-agent.runtime.releasing', {
835
- runtimeId: runtime.runtimeId,
836
- sessionId: runtime.session.id,
837
- reason,
838
- ...(changedCategories.length === 0 ? {} : { changedCategories }),
839
- ...(runtime.activeRunId === undefined ? {} : { runId: runtime.activeRunId })
840
- });
841
- if (runtime.idleTimer !== undefined) {
842
- clearTimeout(runtime.idleTimer);
843
- delete runtime.idleTimer;
844
- }
845
- try {
846
- if (runtime.automaticTitleTask !== undefined) {
847
- runtime.automaticTitleTask.cancel();
848
- await runtime.automaticTitleTask.result;
849
- }
850
- try {
851
- await this.#computerUse?.releaseSession(runtime.session.id);
852
- }
853
- finally {
854
- await runtime.agent.dispose();
855
- }
856
- }
857
- finally {
858
- await runtime.mcpLease.release().catch(() => undefined);
859
- runtime.releaseEnvironment?.();
860
- for (const [sessionId, candidate] of this.#sessionRuntimes)
861
- if (candidate === runtime)
862
- this.#sessionRuntimes.delete(sessionId);
863
- }
864
- })();
865
- return runtime.release;
866
- }
867
584
  requestQuestion(runId, request, signal = new AbortController().signal) {
868
585
  const questions = normalizeQuestions(request.questions);
869
586
  if (questions.length === 0)
@@ -980,7 +697,7 @@ export class MarAgentManagedRunController {
980
697
  itemId: boundedConversationItemId('mar-agent-message', event.itemId)
981
698
  });
982
699
  else {
983
- let normalizer = this.#normalizers.get(runId) ?? this.#sessionRuntimes.get(event.sessionId)?.normalizer;
700
+ let normalizer = this.#normalizers.get(runId) ?? this.#runtimes.findRuntime(event.sessionId)?.normalizer;
984
701
  if (normalizer === undefined) {
985
702
  normalizer = new MarAgentConversationNormalizer();
986
703
  this.#normalizers.set(runId, normalizer);
@@ -1008,77 +725,6 @@ function contextUsageChanged(event) {
1008
725
  event.purpose !== 'compact' &&
1009
726
  (event.contextInputTokens !== undefined || event.inputTokens !== undefined)));
1010
727
  }
1011
- function isExecutionScopedMcp(server) {
1012
- return (server.lifecycle === 'EXECUTION' ||
1013
- (server.transport === 'STDIO' && server.environment?.['MAR_CTL_USER_TOKEN'] !== undefined));
1014
- }
1015
- function sessionHostFingerprint(input, hostConfiguration) {
1016
- const configuration = {
1017
- cwd: input.cwd,
1018
- mcpServers: input.mcpServers.filter((server) => !isExecutionScopedMcp(server)),
1019
- environment: Object.entries(hostConfiguration.environment)
1020
- .filter((entry) => entry[1] !== undefined)
1021
- .sort(([left], [right]) => left.localeCompare(right)),
1022
- guaranteedCommands: Object.entries(hostConfiguration.guaranteedCommands).sort(([left], [right]) => left.localeCompare(right))
1023
- };
1024
- const digest = (value) => createHash('sha256').update(canonicalJson(value)).digest('hex');
1025
- return {
1026
- value: digest(configuration),
1027
- // Only fixed category names leave this comparison; never log these value fingerprints.
1028
- categories: {
1029
- cwd: digest(configuration.cwd),
1030
- mcpServers: digest(configuration.mcpServers),
1031
- environment: digest(configuration.environment),
1032
- guaranteedCommands: digest(configuration.guaranteedCommands)
1033
- }
1034
- };
1035
- }
1036
- function sessionModelFingerprint(input) {
1037
- const compact = marAgentRuntimeCompactConfigurationSchema.safeParse(input.compact);
1038
- return createHash('sha256')
1039
- .update(JSON.stringify({
1040
- defaultModelId: input.model.id,
1041
- compact: compact.success
1042
- ? {
1043
- model: runtimeModelFingerprint(compact.data.model),
1044
- effort: compact.data.reasoningEffort
1045
- }
1046
- : null,
1047
- models: input.models.map(runtimeModelFingerprint)
1048
- }))
1049
- .digest('hex');
1050
- }
1051
- async function resolveMarAgentHostRuntimeConfiguration(secretEnvironment = {}) {
1052
- const environment = nodeExecutionEnvironment({
1053
- ...secretEnvironment,
1054
- MAR_CTL_CONTEXT_REQUIRED: '1'
1055
- });
1056
- // CLI credentials are bound to each execution, never retained as Host configuration.
1057
- delete environment['MAR_CTL_USER_TOKEN'];
1058
- for (const key of [
1059
- 'MAR_CTL_NODE_ID',
1060
- 'MAR_CTL_WORKSPACE_ID',
1061
- 'MAR_CTL_SESSION_ID',
1062
- 'MAR_CTL_RUN_ID',
1063
- 'MAR_CTL_RUNNER'
1064
- ])
1065
- delete environment[key];
1066
- const ripgrep = await availableBundledRipgrep();
1067
- return {
1068
- environment: ripgrep === undefined ? environment : prependBundledRipgrepToPath(environment, ripgrep),
1069
- guaranteedCommands: ripgrep === undefined ? {} : { rg: BUNDLED_RIPGREP_VERSION }
1070
- };
1071
- }
1072
- async function availableBundledRipgrep() {
1073
- try {
1074
- const executable = bundledRipgrepPath();
1075
- await access(executable, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
1076
- return executable;
1077
- }
1078
- catch {
1079
- return undefined;
1080
- }
1081
- }
1082
728
  function normalizeQuestions(raw) {
1083
729
  return raw.flatMap((value) => {
1084
730
  if (value === null || typeof value !== 'object' || Array.isArray(value))
@@ -1132,44 +778,3 @@ export function resolveRunModels(marAgentModel, marAgentModels) {
1132
778
  return true;
1133
779
  });
1134
780
  }
1135
- function runtimeModelFingerprint(model) {
1136
- const { apiKey, credential, imageGeneration, ...configuration } = model;
1137
- return {
1138
- ...configuration,
1139
- ...(imageGeneration === undefined
1140
- ? {}
1141
- : {
1142
- imageGeneration: {
1143
- ...imageGeneration,
1144
- credential: runtimeCredentialFingerprint(imageGeneration.credential)
1145
- }
1146
- }),
1147
- ...(apiKey === undefined
1148
- ? {}
1149
- : { apiKeyHash: createHash('sha256').update(apiKey).digest('hex') }),
1150
- credential: runtimeCredentialFingerprint(credential)
1151
- };
1152
- }
1153
- function runtimeCredentialFingerprint(credential) {
1154
- return credential?.type === 'INLINE_API_KEY'
1155
- ? {
1156
- type: credential.type,
1157
- valueHash: createHash('sha256').update(credential.value).digest('hex')
1158
- }
1159
- : credential === undefined
1160
- ? undefined
1161
- : {
1162
- type: credential.type,
1163
- credentialId: credential.credentialId
1164
- };
1165
- }
1166
- async function closeModelCredentialProviders(models) {
1167
- await Promise.allSettled([
1168
- ...new Set(models.flatMap((model) => [
1169
- ...(model.credentialProvider === undefined ? [] : [model.credentialProvider]),
1170
- ...(model.imageGeneration?.credentialProvider === undefined
1171
- ? []
1172
- : [model.imageGeneration.credentialProvider])
1173
- ]))
1174
- ].map((provider) => provider.close()));
1175
- }