@myagentroam/node 0.10.40 → 0.10.41
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/claude-agent-sdk.js +8 -5
- package/dist/connector.js +36 -72
- package/dist/mcp/configuration.js +107 -0
- package/dist/mcp/naming.js +42 -0
- package/dist/runner/abstract-runner.js +3 -154
- package/dist/runner/claude/managed-run-controller.js +1 -4
- package/dist/runner/claude-code-runner.js +2 -2
- package/dist/runner/codex/managed-run-controller.js +6 -8
- package/dist/runner/codex-runner.js +2 -2
- package/dist/runner/mar-agent/computer-use-skill.js +6 -3
- package/dist/runner/mar-agent/computer-use.js +2 -1
- package/dist/runner/mar-agent/managed-run-controller.js +10 -11
- package/dist/runner/mar-agent/mcp-client-pool.js +3 -5
- package/dist/runner/opencode/managed-run-controller.js +1 -4
- package/dist/runner/opencode-runner.js +2 -2
- package/dist/service/continuous-run-coordinator.js +11 -18
- package/dist/service/{goal-mcp-server.js → goal-mcp-bridge.js} +19 -14
- package/dist/service/goal-mcp-stdio.js +5 -4
- package/dist/service/goal-runtime.js +21 -36
- package/dist/service/hosted-conversation-node-service.js +2 -0
- package/dist/service/mcp-installation-verifier.js +2 -1
- package/dist/service/mcp-node-operation-service.js +3 -1
- package/dist/service/{mail-notification-mcp-server.js → notification-mcp-bridge.js} +30 -22
- package/dist/service/{mail-notification-mcp-stdio.js → notification-mcp-stdio.js} +9 -8
- package/dist/service/restart-session-restorer.js +2 -2
- package/dist/service/{scheduled-task-mcp-server.js → schedule-mcp-bridge.js} +25 -19
- package/dist/service/{scheduled-task-mcp-stdio.js → schedule-mcp-stdio.js} +8 -7
- package/dist/service/session-catalog-service.js +3 -3
- package/package.json +4 -4
- /package/dist/{mcp-installation-model.js → mcp/model.js} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_MCP } from '../../mcp/naming.js';
|
|
1
2
|
import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
|
|
2
3
|
import { extractId } from '../../util/runner-native-session-parsers.js';
|
|
3
4
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
@@ -204,8 +205,8 @@ export class CodexManagedRunController {
|
|
|
204
205
|
}
|
|
205
206
|
if (notification.requestId !== undefined &&
|
|
206
207
|
notification.method === 'mcpServer/elicitation/request' &&
|
|
207
|
-
((activeRun.goalMcp === true && params.serverName ===
|
|
208
|
-
(activeRun.scheduleMcp === true && params.serverName ===
|
|
208
|
+
((activeRun.goalMcp === true && params.serverName === NODE_MCP.goal.name) ||
|
|
209
|
+
(activeRun.scheduleMcp === true && params.serverName === NODE_MCP.schedule.name)) &&
|
|
209
210
|
isPlainRecord(params._meta) &&
|
|
210
211
|
params._meta.codex_approval_kind === 'mcp_tool_call') {
|
|
211
212
|
this.runner.respond(notification.requestId, {
|
|
@@ -559,11 +560,9 @@ export class CodexManagedRunController {
|
|
|
559
560
|
const { runId, sessionId, input, cwd } = payload;
|
|
560
561
|
let attachments;
|
|
561
562
|
let secretEnvironment;
|
|
562
|
-
let mcpSecretEnvironment;
|
|
563
563
|
try {
|
|
564
564
|
attachments = this.host.parseAttachments(payload.attachments);
|
|
565
565
|
secretEnvironment = parseSecretEnvironment(payload.secretEnvironment);
|
|
566
|
-
mcpSecretEnvironment = parseSecretEnvironment(payload.mcpSecretEnvironment);
|
|
567
566
|
}
|
|
568
567
|
catch (error) {
|
|
569
568
|
this.emitError(runId, error, 'CODEX_RUN_FAILED');
|
|
@@ -615,9 +614,8 @@ export class CodexManagedRunController {
|
|
|
615
614
|
: {}),
|
|
616
615
|
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, {
|
|
617
616
|
...secretEnvironment,
|
|
618
|
-
...mcpSecretEnvironment,
|
|
619
617
|
MAR_USERNAME: secretEnvironment['MAR_USERNAME'] ?? ''
|
|
620
|
-
})
|
|
618
|
+
}, payload.executionMcpServers)
|
|
621
619
|
}, attachments);
|
|
622
620
|
}
|
|
623
621
|
async run(payload, attachments) {
|
|
@@ -733,10 +731,10 @@ export class CodexManagedRunController {
|
|
|
733
731
|
}
|
|
734
732
|
}
|
|
735
733
|
function hasGoalMcp(value) {
|
|
736
|
-
return isPlainRecord(value) && isPlainRecord(value.
|
|
734
|
+
return isPlainRecord(value) && isPlainRecord(value[NODE_MCP.goal.name]);
|
|
737
735
|
}
|
|
738
736
|
function hasScheduleMcp(value) {
|
|
739
|
-
return isPlainRecord(value) && isPlainRecord(value.
|
|
737
|
+
return isPlainRecord(value) && isPlainRecord(value[NODE_MCP.schedule.name]);
|
|
740
738
|
}
|
|
741
739
|
function codexOutputTokens(value) {
|
|
742
740
|
if (!isPlainRecord(value) ||
|
|
@@ -215,8 +215,8 @@ export class CodexRunner extends AbstractRunner {
|
|
|
215
215
|
serviceTier() {
|
|
216
216
|
return this.fastEnabled ? 'fast' : undefined;
|
|
217
217
|
}
|
|
218
|
-
mcpConfiguration(raw, secrets) {
|
|
219
|
-
const servers = super.mcpConfiguration(raw, secrets);
|
|
218
|
+
mcpConfiguration(raw, secrets, executionServers = []) {
|
|
219
|
+
const servers = super.mcpConfiguration(raw, secrets, executionServers);
|
|
220
220
|
return Object.fromEntries(servers.map((server) => [
|
|
221
221
|
server.name,
|
|
222
222
|
server.transport === 'STDIO'
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import { NODE_COMPUTER_TOOL_NAME } from '../../mcp/naming.js';
|
|
1
2
|
export const computerUseHostSkill = {
|
|
2
3
|
name: 'computer-use',
|
|
3
|
-
description: 'Use when the user asks to operate or verify a real desktop GUI through
|
|
4
|
-
|
|
4
|
+
description: 'Use when the user asks to operate or verify a real desktop GUI through ' +
|
|
5
|
+
NODE_COMPUTER_TOOL_NAME +
|
|
6
|
+
'. Load before the first desktop action.',
|
|
7
|
+
requiredTools: [NODE_COMPUTER_TOOL_NAME],
|
|
5
8
|
load: async () => computerUseSkill()
|
|
6
9
|
};
|
|
7
10
|
export function computerUseSkill() {
|
|
8
11
|
return `## Operating the desktop
|
|
9
12
|
|
|
10
|
-
Use \`
|
|
13
|
+
Use \`${NODE_COMPUTER_TOOL_NAME}\` only for user-authorized interaction with applications on the current desktop, or when the requested result can only be verified through the real GUI. Prefer ordinary workspace and shell tools for file and code work that does not require the GUI.
|
|
11
14
|
|
|
12
15
|
### Observe before acting
|
|
13
16
|
- Start with one observation-only call using empty \`code\`. Do not repeat observation while the returned state is still current and the next steps do not depend on new visual information.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_COMPUTER_TOOL_NAME } from '../../mcp/naming.js';
|
|
1
2
|
import { spawn } from 'node:child_process';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import { access, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
@@ -7,7 +8,7 @@ import { createInterface } from 'node:readline';
|
|
|
7
8
|
import { terminateProcessTree } from '../../process-tree.js';
|
|
8
9
|
import { ComputerUseControlIndicator } from './computer-use-indicator.js';
|
|
9
10
|
import { PYTHON_PROBE_SOURCE } from './computer-use-probe.js';
|
|
10
|
-
const TOOL_NAME =
|
|
11
|
+
const TOOL_NAME = NODE_COMPUTER_TOOL_NAME;
|
|
11
12
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
12
13
|
const MAX_TIMEOUT_MS = 120_000;
|
|
13
14
|
const MAX_CODE_BYTES = 1024 * 1024;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_MCP } from '../../mcp/naming.js';
|
|
1
2
|
import { computerUseHostSkill } from './computer-use-skill.js';
|
|
2
3
|
import { marctlExecutionEnvironment, marctlHostSkill } from './marctl-skill.js';
|
|
3
4
|
import { compactRuntimeConfigurations, marAgentCompactExtensionSource, updateMarAgentCompactState } from './compact-extensions.js';
|
|
@@ -185,12 +186,10 @@ export class MarAgentManagedRunController {
|
|
|
185
186
|
let secretEnvironment;
|
|
186
187
|
try {
|
|
187
188
|
secretEnvironment = parseSecretEnvironment(payload.secretEnvironment);
|
|
188
|
-
const mcpSecretEnvironment = parseSecretEnvironment(payload.mcpSecretEnvironment);
|
|
189
189
|
mcpServers = this.#resolveMcpServers(payload.mcpInstallations, {
|
|
190
190
|
...secretEnvironment,
|
|
191
|
-
...mcpSecretEnvironment,
|
|
192
191
|
MAR_USERNAME: secretEnvironment['MAR_USERNAME'] ?? ''
|
|
193
|
-
});
|
|
192
|
+
}, payload.executionMcpServers);
|
|
194
193
|
images = parseRunnerImageAttachments(payload.attachments);
|
|
195
194
|
}
|
|
196
195
|
catch {
|
|
@@ -350,7 +349,7 @@ export class MarAgentManagedRunController {
|
|
|
350
349
|
controller.signal.throwIfAborted();
|
|
351
350
|
const userTools = executionMcpLease?.pool.definitions() ?? [];
|
|
352
351
|
const nodeTools = computerUseBinding?.tools ?? [];
|
|
353
|
-
if (userTools.some((tool) => tool.name.startsWith('
|
|
352
|
+
if (userTools.some((tool) => tool.name.startsWith('mcp__mar_computer__')))
|
|
354
353
|
throw new Error('MAR_AGENT_MCP_RESERVED_NAMESPACE');
|
|
355
354
|
if (new Set([...userTools, ...nodeTools].map((tool) => tool.name)).size !==
|
|
356
355
|
userTools.length + nodeTools.length)
|
|
@@ -391,8 +390,8 @@ export class MarAgentManagedRunController {
|
|
|
391
390
|
};
|
|
392
391
|
const computer = nodeTools.map((definition) => ({
|
|
393
392
|
definition,
|
|
394
|
-
serverId:
|
|
395
|
-
rawName:
|
|
393
|
+
serverId: NODE_MCP.computer.name,
|
|
394
|
+
rawName: NODE_MCP.computer.tools.exec,
|
|
396
395
|
sourceIdentity: `node:${input.runId}:computer`,
|
|
397
396
|
definitionRevision: createHash('sha256')
|
|
398
397
|
.update(JSON.stringify(definition))
|
|
@@ -404,7 +403,9 @@ export class MarAgentManagedRunController {
|
|
|
404
403
|
revision: `${external.revision}:${input.runId}`,
|
|
405
404
|
servers: [
|
|
406
405
|
...external.servers,
|
|
407
|
-
...(computer.length
|
|
406
|
+
...(computer.length
|
|
407
|
+
? [{ id: NODE_MCP.computer.name, name: NODE_MCP.computer.name }]
|
|
408
|
+
: [])
|
|
408
409
|
],
|
|
409
410
|
tools: [...external.tools, ...computer]
|
|
410
411
|
};
|
|
@@ -753,10 +754,8 @@ export class MarAgentManagedRunController {
|
|
|
753
754
|
throw error;
|
|
754
755
|
}
|
|
755
756
|
}
|
|
756
|
-
#resolveMcpServers(installations, environment) {
|
|
757
|
-
const servers = this.runner.mcpConfiguration(installations, environment);
|
|
758
|
-
if (servers.some((server) => server.name === 'computer'))
|
|
759
|
-
throw new Error('MAR_AGENT_MCP_RESERVED_NAMESPACE');
|
|
757
|
+
#resolveMcpServers(installations, environment, executionServers = []) {
|
|
758
|
+
const servers = this.runner.mcpConfiguration(installations, environment, executionServers);
|
|
760
759
|
return servers;
|
|
761
760
|
}
|
|
762
761
|
#runtimeInUse(runtime) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mcpNameAlias, NODE_MCP_CLIENT_INFO } from '../../mcp/naming.js';
|
|
1
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
3
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
4
|
import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
@@ -327,7 +328,7 @@ export class MarAgentMcpClientPool {
|
|
|
327
328
|
if (bytes > MCP_CATALOG_LIMITS.maximumBytes)
|
|
328
329
|
throw new Error('MAR_AGENT_MCP_CATALOG_LIMIT');
|
|
329
330
|
for (const tool of listed.tools) {
|
|
330
|
-
const name = `mcp__${
|
|
331
|
+
const name = `mcp__${mcpNameAlias(state.server.name)}__${mcpNameAlias(tool.name)}`;
|
|
331
332
|
if (tools.has(name))
|
|
332
333
|
throw new Error('MAR_AGENT_MCP_TOOL_NAME_CONFLICT');
|
|
333
334
|
const definition = {
|
|
@@ -371,7 +372,7 @@ export class MarAgentMcpClientPool {
|
|
|
371
372
|
throw new Error('MAR_AGENT_MCP_CATALOG_LIMIT');
|
|
372
373
|
}
|
|
373
374
|
async #connect(server, cwd, options) {
|
|
374
|
-
const client = new Client(
|
|
375
|
+
const client = new Client(NODE_MCP_CLIENT_INFO);
|
|
375
376
|
this.#clients.push(client);
|
|
376
377
|
let state;
|
|
377
378
|
let initialDirty = false;
|
|
@@ -469,9 +470,6 @@ function mcpConfigurationFingerprint(servers, cwd, authorizationScope) {
|
|
|
469
470
|
}))
|
|
470
471
|
.digest('hex');
|
|
471
472
|
}
|
|
472
|
-
function safeName(value) {
|
|
473
|
-
return value.replace(/[^a-zA-Z0-9_-]/gu, '_').slice(0, 64);
|
|
474
|
-
}
|
|
475
473
|
function requireMcpOwner(owner) {
|
|
476
474
|
if (owner === undefined)
|
|
477
475
|
throw new Error('MAR_AGENT_MCP_SANDBOX_OWNER_REQUIRED');
|
|
@@ -110,7 +110,6 @@ export class OpenCodeManagedRunController {
|
|
|
110
110
|
this.host.active.has(payload.runId))
|
|
111
111
|
return;
|
|
112
112
|
let environment;
|
|
113
|
-
let mcpSecretEnvironment;
|
|
114
113
|
let mcp;
|
|
115
114
|
let attachments;
|
|
116
115
|
try {
|
|
@@ -118,12 +117,10 @@ export class OpenCodeManagedRunController {
|
|
|
118
117
|
...parseSecretEnvironment(payload.secretEnvironment),
|
|
119
118
|
MAR_CTL_CONTEXT_REQUIRED: '1'
|
|
120
119
|
}, { PATH: process.env['PATH'], Path: process.env['Path'] });
|
|
121
|
-
mcpSecretEnvironment = parseSecretEnvironment(payload.mcpSecretEnvironment);
|
|
122
120
|
mcp = this.runner.mcpConfiguration(payload.mcpInstallations, {
|
|
123
121
|
...environment,
|
|
124
|
-
...mcpSecretEnvironment,
|
|
125
122
|
MAR_USERNAME: environment['MAR_USERNAME'] ?? ''
|
|
126
|
-
});
|
|
123
|
+
}, payload.executionMcpServers);
|
|
127
124
|
attachments = this.host.parseAttachments(payload.attachments);
|
|
128
125
|
}
|
|
129
126
|
catch (error) {
|
|
@@ -168,8 +168,8 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
168
168
|
}, delayMs);
|
|
169
169
|
this.profileRefreshTimer.unref();
|
|
170
170
|
}
|
|
171
|
-
mcpConfiguration(raw, secrets) {
|
|
172
|
-
const servers = super.mcpConfiguration(raw, secrets);
|
|
171
|
+
mcpConfiguration(raw, secrets, executionServers = []) {
|
|
172
|
+
const servers = super.mcpConfiguration(raw, secrets, executionServers);
|
|
173
173
|
return Object.fromEntries(servers.map((server) => [
|
|
174
174
|
server.name,
|
|
175
175
|
server.transport === 'STDIO'
|
|
@@ -381,13 +381,14 @@ export class ContinuousRunCoordinator {
|
|
|
381
381
|
return;
|
|
382
382
|
}
|
|
383
383
|
this.dispatchedExecutions.add(executionId);
|
|
384
|
+
let dispatched = false;
|
|
384
385
|
try {
|
|
385
386
|
// Capabilities belong to this Execution, never the Run or a claimed QueueItem.
|
|
386
387
|
const executionInput = { ...pending, runId: executionId };
|
|
387
388
|
const goalMcp = pending.goal === undefined ? undefined : await this.options.goalMcp?.(executionInput);
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
if (this.suspended || this.options.stopped())
|
|
389
|
+
const scheduleMcp = await this.options.scheduleMcp?.(executionInput);
|
|
390
|
+
const notificationMcp = await this.options.notificationMcp?.(executionInput);
|
|
391
|
+
if (this.suspended || this.options.stopped() || !this.dispatchedExecutions.has(executionId))
|
|
391
392
|
return;
|
|
392
393
|
if (!this.goalCurrent(pending)) {
|
|
393
394
|
this.dispatchedExecutions.delete(executionId);
|
|
@@ -423,15 +424,6 @@ export class ContinuousRunCoordinator {
|
|
|
423
424
|
MAR_CTL_RUN_ID: active.id,
|
|
424
425
|
MAR_CTL_RUNNER: pending.runner
|
|
425
426
|
},
|
|
426
|
-
...(goalMcp === undefined && scheduledMcp === undefined && mailMcp === undefined
|
|
427
|
-
? {}
|
|
428
|
-
: {
|
|
429
|
-
mcpSecretEnvironment: {
|
|
430
|
-
...goalMcp?.secrets,
|
|
431
|
-
...scheduledMcp?.secrets,
|
|
432
|
-
...mailMcp?.secrets
|
|
433
|
-
}
|
|
434
|
-
}),
|
|
435
427
|
...(personalInstructions === undefined ? {} : { personalInstructions }),
|
|
436
428
|
...(pending.marAgentModel === undefined ? {} : { marAgentModel: pending.marAgentModel }),
|
|
437
429
|
...(pending.marAgentModels === undefined ? {} : { marAgentModels: pending.marAgentModels }),
|
|
@@ -442,17 +434,18 @@ export class ContinuousRunCoordinator {
|
|
|
442
434
|
? {}
|
|
443
435
|
: { marAgentRuntimeScopeId: pending.marAgentRuntimeScopeId }),
|
|
444
436
|
...(pending.computerUse === undefined ? {} : { computerUse: pending.computerUse }),
|
|
445
|
-
mcpInstallations:
|
|
446
|
-
|
|
447
|
-
...(goalMcp === undefined ? [] : [goalMcp.installation]),
|
|
448
|
-
...(scheduledMcp === undefined ? [] : [scheduledMcp.installation]),
|
|
449
|
-
...(mailMcp === undefined ? [] : [mailMcp.installation])
|
|
450
|
-
]
|
|
437
|
+
mcpInstallations: this.options.mcps(pending.workspaceId),
|
|
438
|
+
executionMcpServers: [goalMcp, scheduleMcp, notificationMcp].filter((server) => server !== undefined)
|
|
451
439
|
});
|
|
440
|
+
dispatched = true;
|
|
452
441
|
}
|
|
453
442
|
catch (error) {
|
|
454
443
|
this.options.emitRun(executionId, 'run.rejected', { code: safeErrorCode(error, 'RUN_START_FAILED') }, 'FAILED');
|
|
455
444
|
}
|
|
445
|
+
finally {
|
|
446
|
+
if (!dispatched)
|
|
447
|
+
this.options.revokeExecutionMcps?.(executionId);
|
|
448
|
+
}
|
|
456
449
|
}
|
|
457
450
|
executionStarted(executionId) {
|
|
458
451
|
if (this.startedExecutions.has(executionId))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_MCP } from '../mcp/naming.js';
|
|
1
2
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
3
|
import { existsSync } from 'node:fs';
|
|
3
4
|
import { createServer } from 'node:net';
|
|
@@ -5,7 +6,7 @@ import { tmpdir } from 'node:os';
|
|
|
5
6
|
import { join } from 'node:path';
|
|
6
7
|
import { unlink } from 'node:fs/promises';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export class
|
|
9
|
+
export class GoalMcpBridge {
|
|
9
10
|
goals;
|
|
10
11
|
changed;
|
|
11
12
|
capabilities = new Map();
|
|
@@ -26,22 +27,26 @@ export class GoalMcpServer {
|
|
|
26
27
|
});
|
|
27
28
|
return this.listening;
|
|
28
29
|
}
|
|
29
|
-
async
|
|
30
|
+
async prepare(capability) {
|
|
30
31
|
await this.listen();
|
|
32
|
+
const args = goalMcpStdioArgs();
|
|
31
33
|
const token = randomBytes(32).toString('base64url');
|
|
32
|
-
const secretName = `MAR_GOAL_MCP_${randomBytes(16).toString('hex').toUpperCase()}`;
|
|
33
34
|
this.capabilities.set(token, capability);
|
|
34
35
|
return {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
name: NODE_MCP.goal.name,
|
|
37
|
+
origin: 'BUILTIN',
|
|
38
|
+
lifecycle: 'EXECUTION',
|
|
39
|
+
transport: 'STDIO',
|
|
40
|
+
command: process.execPath,
|
|
41
|
+
args,
|
|
42
|
+
environment: {
|
|
43
|
+
MAR_GOAL_MCP_ENDPOINT: this.endpoint,
|
|
44
|
+
MAR_GOAL_MCP_TOKEN: token
|
|
43
45
|
},
|
|
44
|
-
|
|
46
|
+
allowedToolCallers: {
|
|
47
|
+
[NODE_MCP.goal.tools.get]: ['direct', 'programmatic'],
|
|
48
|
+
[NODE_MCP.goal.tools.update]: ['direct', 'programmatic']
|
|
49
|
+
}
|
|
45
50
|
};
|
|
46
51
|
}
|
|
47
52
|
async close() {
|
|
@@ -89,11 +94,11 @@ export class GoalMcpServer {
|
|
|
89
94
|
if (capability === undefined)
|
|
90
95
|
throw new Error('GOAL_CAPABILITY_INVALID');
|
|
91
96
|
const current = this.current(capability);
|
|
92
|
-
if (request.tool ===
|
|
97
|
+
if (request.tool === NODE_MCP.goal.tools.get) {
|
|
93
98
|
socket.end(`${JSON.stringify({ result: current })}\n`);
|
|
94
99
|
return;
|
|
95
100
|
}
|
|
96
|
-
if (request.tool !==
|
|
101
|
+
if (request.tool !== NODE_MCP.goal.tools.update)
|
|
97
102
|
throw new Error('GOAL_TOOL_INVALID');
|
|
98
103
|
const status = request.arguments !== null &&
|
|
99
104
|
typeof request.arguments === 'object' &&
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_MCP, NODE_MCP_VERSION } from '../mcp/naming.js';
|
|
1
2
|
import { connect } from 'node:net';
|
|
2
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -6,12 +7,12 @@ const endpoint = process.env['MAR_GOAL_MCP_ENDPOINT'];
|
|
|
6
7
|
const token = process.env['MAR_GOAL_MCP_TOKEN'];
|
|
7
8
|
if (endpoint === undefined || token === undefined)
|
|
8
9
|
throw new Error('GOAL_CAPABILITY_INVALID');
|
|
9
|
-
const server = new McpServer({ name:
|
|
10
|
-
server.registerTool(
|
|
11
|
-
server.registerTool(
|
|
10
|
+
const server = new McpServer({ name: NODE_MCP.goal.name, version: NODE_MCP_VERSION });
|
|
11
|
+
server.registerTool(NODE_MCP.goal.tools.get, { description: 'Get the authoritative current Goal, status, and objective.' }, async () => result(await request(NODE_MCP.goal.tools.get, {})));
|
|
12
|
+
server.registerTool(NODE_MCP.goal.tools.update, {
|
|
12
13
|
description: 'Explicitly mark the current Goal completed or blocked.',
|
|
13
14
|
inputSchema: { status: z.enum(['completed', 'blocked']) }
|
|
14
|
-
}, async ({ status }) => result(await request(
|
|
15
|
+
}, async ({ status }) => result(await request(NODE_MCP.goal.tools.update, { status })));
|
|
15
16
|
await server.connect(new StdioServerTransport());
|
|
16
17
|
function request(tool, args) {
|
|
17
18
|
return new Promise((resolve, reject) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_MCP } from '../mcp/naming.js';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { parseComposerTags, serializeComposerTag } from '@myagentroam/protocol';
|
|
3
4
|
export function goalUnrestrictedAccess(runner, currentAccess) {
|
|
@@ -9,47 +10,25 @@ export function goalUnrestrictedAccess(runner, currentAccess) {
|
|
|
9
10
|
return 'bypass';
|
|
10
11
|
return 'auto';
|
|
11
12
|
}
|
|
12
|
-
const START_OPEN = '<mar_goal_start>';
|
|
13
|
-
const START_CLOSE = '</mar_goal_start>';
|
|
14
|
-
const CONTINUE_OPEN = '<mar_goal_continue>';
|
|
15
|
-
const CONTINUE_CLOSE = '</mar_goal_continue>';
|
|
16
|
-
function goalPromptTags(text) {
|
|
17
|
-
if (text.startsWith(START_OPEN))
|
|
18
|
-
return { open: START_OPEN, close: START_CLOSE, continuation: false };
|
|
19
|
-
if (text.startsWith(CONTINUE_OPEN))
|
|
20
|
-
return { open: CONTINUE_OPEN, close: CONTINUE_CLOSE, continuation: true };
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
13
|
export function publicGoalStartText(objective) {
|
|
24
14
|
return serializeComposerTag('mar_goal_start', { objective: objective.trim() });
|
|
25
15
|
}
|
|
26
16
|
export function stripGoalStartPrompt(text) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return close === -1 ? '' : text.slice(close + tags.close.length).trimStart();
|
|
17
|
+
const first = parseComposerTags(text)[0];
|
|
18
|
+
return first?.kind === 'mar_goal_start' || first?.kind === 'mar_goal_continue'
|
|
19
|
+
? text.slice(first.raw.length).trimStart()
|
|
20
|
+
: text;
|
|
32
21
|
}
|
|
33
22
|
export function publicGoalHistoryText(text) {
|
|
34
|
-
|
|
23
|
+
const first = parseComposerTags(text)[0];
|
|
24
|
+
if (first?.kind === 'mar_restart')
|
|
35
25
|
return serializeComposerTag('mar_restart', {});
|
|
36
|
-
|
|
37
|
-
if (tags === undefined)
|
|
38
|
-
return text;
|
|
39
|
-
const safe = parseComposerTags(text)[0];
|
|
40
|
-
if (safe?.kind === 'mar_goal_start' || safe?.kind === 'mar_goal_continue')
|
|
26
|
+
if (first?.kind !== 'mar_goal_start' && first?.kind !== 'mar_goal_continue')
|
|
41
27
|
return text;
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const prompt = close === -1 ? text : text.slice(0, close);
|
|
47
|
-
const objectiveStart = prompt.indexOf('\nOriginal objective:\n');
|
|
48
|
-
const objectiveEnd = prompt.lastIndexOf('\n\nContinue executing this Goal across turns:');
|
|
49
|
-
const objective = objectiveStart === -1 || objectiveEnd === -1
|
|
50
|
-
? ''
|
|
51
|
-
: prompt.slice(objectiveStart + '\nOriginal objective:\n'.length, objectiveEnd);
|
|
52
|
-
return [publicGoalStartText(objective), stripped].filter(Boolean).join('\n');
|
|
28
|
+
const marker = first.kind === 'mar_goal_start'
|
|
29
|
+
? serializeComposerTag('mar_goal_start', { objective: first.payload.objective })
|
|
30
|
+
: serializeComposerTag('mar_goal_continue', {});
|
|
31
|
+
return [marker, stripGoalStartPrompt(text)].filter(Boolean).join('\n');
|
|
53
32
|
}
|
|
54
33
|
export class GoalRuntime {
|
|
55
34
|
changed;
|
|
@@ -237,9 +216,15 @@ export class GoalRuntime {
|
|
|
237
216
|
if (goal?.status !== 'active')
|
|
238
217
|
throw new Error('GOAL_NOT_ACTIVE');
|
|
239
218
|
const heading = kind === 'start' ? 'A Goal has started or changed.' : 'Continue the active Goal.';
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
219
|
+
const instruction = `${heading}
|
|
220
|
+
|
|
221
|
+
Continue executing this Goal across turns:
|
|
222
|
+
- This Goal is owned by the Node's ${NODE_MCP.goal.name} MCP server. Prefer ${NODE_MCP.goal.name}.${NODE_MCP.goal.tools.update} to complete it. In Codex, a runner-native Goal created during this Goal may also complete it when Codex reports that native Goal complete; native Goal state from another task or turn does not count. Do not claim completion based only on a final message or an unrelated Goal.\n- Work on the objective now. If it is incomplete, use tools to finish a coherent unit or obtain decisive evidence; do not stop at an acknowledgement, status, plan, or promise.\n- Preserve the original scope. Before completion, review the full objective and verify it with real evidence, then call ${NODE_MCP.goal.tools.update} with status=completed when available.\n- Adapt and retry after individual failures. Request blocked only after the same blocker persists for three turns and the required additional retry.\n- Use ${NODE_MCP.goal.tools.get} only when you need to reread the authoritative objective or status.\n- After ${NODE_MCP.goal.tools.update} confirms completed or finally blocked, summarize the completed work, verification evidence, remaining issues, and the tool-reported elapsedWallMs converted from milliseconds into a concise readable duration in your final user-facing response. If Codex instead confirms completion with its native Goal, summarize the work without claiming a MAR elapsedWallMs value. Name whether the Goal completed or is finally blocked. MAR elapsedWallMs includes waiting and retries through the status update, not tool execution time or the final response. If blocked returns active for the required retry, continue work instead of claiming a final summary.`;
|
|
223
|
+
const marker = serializeComposerTag(kind === 'start' ? 'mar_goal_start' : 'mar_goal_continue', {
|
|
224
|
+
objective: goal.objective,
|
|
225
|
+
instruction
|
|
226
|
+
});
|
|
227
|
+
return [marker, goal.inputTags].filter(Boolean).join('\n');
|
|
243
228
|
}
|
|
244
229
|
requireCurrent(sessionId, goalId, revision) {
|
|
245
230
|
const current = this.goals.get(sessionId);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertMcpServerNames } from '../mcp/naming.js';
|
|
1
2
|
import { modelUsageGrantSchema, steerComposerMessage } from '@myagentroam/protocol';
|
|
2
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
4
|
import { constants } from 'node:fs';
|
|
@@ -1891,6 +1892,7 @@ function resolveMcpServers(version, environment) {
|
|
|
1891
1892
|
}
|
|
1892
1893
|
return resolved;
|
|
1893
1894
|
};
|
|
1895
|
+
assertMcpServerNames(version.mcpServers.map((server) => ({ name: server.serverName })));
|
|
1894
1896
|
return version.mcpServers.flatMap((server) => {
|
|
1895
1897
|
if (server.runtime.transport === 'HTTP') {
|
|
1896
1898
|
if (version.draft.networkMode !== 'UNRESTRICTED')
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { NODE_MCP_CLIENT_INFO } from '../mcp/naming.js';
|
|
1
2
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
3
|
import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
4
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
5
|
const INSTALL_TIMEOUT_MS = 90_000;
|
|
5
6
|
export class McpInstallationVerifier {
|
|
6
7
|
async verify(input) {
|
|
7
|
-
const client = new Client(
|
|
8
|
+
const client = new Client(NODE_MCP_CLIENT_INFO);
|
|
8
9
|
try {
|
|
9
10
|
await withTimeout((async () => {
|
|
10
11
|
if (input.entry.runtime.transport === 'STDIO') {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertMcpServerNames, NODE_MCP } from '../mcp/naming.js';
|
|
1
2
|
import { mcpCatalogEntrySchema, mcpInstallationInputSchema, mcpTargetInspectionSchema } from '@myagentroam/protocol';
|
|
2
3
|
import { homedir } from 'node:os';
|
|
3
4
|
import { McpInstallationVerifier } from './mcp-installation-verifier.js';
|
|
@@ -43,7 +44,7 @@ export class McpNodeOperationService {
|
|
|
43
44
|
targetKind,
|
|
44
45
|
installations,
|
|
45
46
|
...(targetKind === 'WORKSPACE'
|
|
46
|
-
? { executionBuiltIns: [
|
|
47
|
+
? { executionBuiltIns: [NODE_MCP.schedule.name, NODE_MCP.notification.name] }
|
|
47
48
|
: {})
|
|
48
49
|
});
|
|
49
50
|
}
|
|
@@ -51,6 +52,7 @@ export class McpNodeOperationService {
|
|
|
51
52
|
if (!isRecord(raw))
|
|
52
53
|
throw new Error('MCP_INSTALL_INPUT_INVALID');
|
|
53
54
|
const entry = mcpCatalogEntrySchema.parse(raw['entry']);
|
|
55
|
+
assertMcpServerNames([{ name: entry.serverName }]);
|
|
54
56
|
const input = mcpInstallationInputSchema.parse({
|
|
55
57
|
catalogEntryId: entry.id,
|
|
56
58
|
enabled: raw['enabled'] ?? true,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_MCP } from '../mcp/naming.js';
|
|
1
2
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
3
|
import { existsSync } from 'node:fs';
|
|
3
4
|
import { unlink } from 'node:fs/promises';
|
|
@@ -6,38 +7,45 @@ import { tmpdir } from 'node:os';
|
|
|
6
7
|
import { join } from 'node:path';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
9
|
import { notificationInputSchema, notificationChannelsRequestSchema, mailNotificationRequestSchema } from '@myagentroam/protocol';
|
|
9
|
-
export class
|
|
10
|
+
export class NotificationMcpBridge {
|
|
10
11
|
resolve;
|
|
11
12
|
send;
|
|
12
13
|
channels;
|
|
13
14
|
capabilities = new Map();
|
|
14
15
|
sockets = new Set();
|
|
15
16
|
endpoint = process.platform === 'win32'
|
|
16
|
-
? `\\\\.\\pipe\\mar-
|
|
17
|
-
: join(tmpdir(), `mar-
|
|
17
|
+
? `\\\\.\\pipe\\mar-notification-${process.pid}-${randomUUID()}`
|
|
18
|
+
: join(tmpdir(), `mar-notification-${process.pid}-${randomUUID()}.sock`);
|
|
18
19
|
listening;
|
|
19
20
|
constructor(resolve, send, channels) {
|
|
20
21
|
this.resolve = resolve;
|
|
21
22
|
this.send = send;
|
|
22
23
|
this.channels = channels;
|
|
23
24
|
}
|
|
24
|
-
async
|
|
25
|
+
async prepare(capability) {
|
|
25
26
|
await this.listen();
|
|
27
|
+
const args = notificationMcpStdioArgs();
|
|
26
28
|
const token = randomBytes(32).toString('base64url');
|
|
27
|
-
const
|
|
29
|
+
const notificationChannels = this.channels?.supported() === true;
|
|
28
30
|
this.capabilities.set(token, capability);
|
|
29
31
|
return {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
name: NODE_MCP.notification.name,
|
|
33
|
+
origin: 'BUILTIN',
|
|
34
|
+
lifecycle: 'EXECUTION',
|
|
35
|
+
transport: 'STDIO',
|
|
36
|
+
command: process.execPath,
|
|
37
|
+
args,
|
|
38
|
+
environment: {
|
|
39
|
+
MAR_NOTIFICATION_MCP_ENDPOINT: this.endpoint,
|
|
40
|
+
MAR_NOTIFICATION_MCP_TOKEN: token,
|
|
41
|
+
MAR_NOTIFICATION_MCP_CHANNELS: notificationChannels ? '1' : '0'
|
|
39
42
|
},
|
|
40
|
-
|
|
43
|
+
allowedToolCallers: {
|
|
44
|
+
[NODE_MCP.notification.tools.email]: ['direct', 'programmatic'],
|
|
45
|
+
...(notificationChannels
|
|
46
|
+
? { [NODE_MCP.notification.tools.send]: ['direct', 'programmatic'] }
|
|
47
|
+
: {})
|
|
48
|
+
}
|
|
41
49
|
};
|
|
42
50
|
}
|
|
43
51
|
revoke(executionId) {
|
|
@@ -97,7 +105,7 @@ export class MailNotificationMcpServer {
|
|
|
97
105
|
const current = this.resolve(capability);
|
|
98
106
|
if (current === undefined)
|
|
99
107
|
throw new Error('MAIL_CAPABILITY_STALE');
|
|
100
|
-
if (request['tool'] ===
|
|
108
|
+
if (request['tool'] === NODE_MCP.notification.tools.send) {
|
|
101
109
|
if (this.channels?.supported() !== true)
|
|
102
110
|
throw new Error('NOTIFICATION_UNSUPPORTED');
|
|
103
111
|
const args = request['arguments'];
|
|
@@ -123,7 +131,7 @@ export class MailNotificationMcpServer {
|
|
|
123
131
|
socket.end(`${JSON.stringify({ result })}\n`);
|
|
124
132
|
return;
|
|
125
133
|
}
|
|
126
|
-
if (request['tool'] !==
|
|
134
|
+
if (request['tool'] !== NODE_MCP.notification.tools.email)
|
|
127
135
|
throw new Error('MAIL_TOOL_INVALID');
|
|
128
136
|
const args = request['arguments'];
|
|
129
137
|
if (args === null || typeof args !== 'object' || Array.isArray(args))
|
|
@@ -157,7 +165,7 @@ export class MailNotificationMcpServer {
|
|
|
157
165
|
}
|
|
158
166
|
}
|
|
159
167
|
}
|
|
160
|
-
export function
|
|
168
|
+
export function resolveNotificationCapability(runtime, capability) {
|
|
161
169
|
const run = runtime.getRun(capability.executionId);
|
|
162
170
|
const session = runtime.getAgentSession(capability.sessionId);
|
|
163
171
|
if (run === undefined ||
|
|
@@ -171,12 +179,12 @@ export function resolveMailNotificationCapability(runtime, capability) {
|
|
|
171
179
|
return undefined;
|
|
172
180
|
return { ...capability, sessionId: session.id };
|
|
173
181
|
}
|
|
174
|
-
function
|
|
175
|
-
const javascript = fileURLToPath(new URL('./
|
|
182
|
+
function notificationMcpStdioArgs() {
|
|
183
|
+
const javascript = fileURLToPath(new URL('./notification-mcp-stdio.js', import.meta.url));
|
|
176
184
|
if (existsSync(javascript))
|
|
177
185
|
return [javascript];
|
|
178
|
-
const typescript = fileURLToPath(new URL('./
|
|
186
|
+
const typescript = fileURLToPath(new URL('./notification-mcp-stdio.ts', import.meta.url));
|
|
179
187
|
if (!existsSync(typescript))
|
|
180
|
-
throw new Error('
|
|
188
|
+
throw new Error('NOTIFICATION_MCP_STDIO_ENTRY_UNAVAILABLE');
|
|
181
189
|
return ['--import', import.meta.resolve('tsx'), typescript];
|
|
182
190
|
}
|