@myagentroam/node 0.10.61 → 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.
- package/dist/connector.js +103 -873
- package/dist/runner/claude-code-runner.js +15 -1
- package/dist/runner/mar-agent/managed-run-controller.js +126 -521
- package/dist/runner/mar-agent/session-runtime-registry.js +271 -0
- package/dist/runner/mar-agent/session-runtime.js +282 -0
- package/dist/service/conversation-hosting-lifecycle-service.js +195 -0
- package/dist/service/conversation-observation.js +240 -0
- package/dist/service/goal-continuation-coordinator.js +622 -0
- package/dist/service/native-session-watch-service.js +88 -270
- package/dist/service/restart-session-restorer.js +1 -1
- package/dist/service/runner-interaction-service.js +27 -1
- package/dist/service/scheduled-session-coordinator.js +119 -48
- package/package.json +3 -3
|
@@ -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 {
|
|
11
|
-
import {
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
#
|
|
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.#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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.
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
this.
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
-
|
|
657
|
-
(
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
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.#
|
|
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
|
-
}
|