@myagentroam/node 0.10.40 → 0.10.42

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.
Files changed (30) hide show
  1. package/dist/claude-agent-sdk.js +8 -5
  2. package/dist/connector.js +36 -72
  3. package/dist/mcp/configuration.js +107 -0
  4. package/dist/mcp/naming.js +42 -0
  5. package/dist/runner/abstract-runner.js +3 -154
  6. package/dist/runner/claude/managed-run-controller.js +1 -4
  7. package/dist/runner/claude-code-runner.js +2 -2
  8. package/dist/runner/codex/managed-run-controller.js +6 -8
  9. package/dist/runner/codex-runner.js +2 -2
  10. package/dist/runner/mar-agent/computer-use-skill.js +6 -3
  11. package/dist/runner/mar-agent/computer-use.js +2 -1
  12. package/dist/runner/mar-agent/managed-run-controller.js +10 -11
  13. package/dist/runner/mar-agent/mcp-client-pool.js +3 -5
  14. package/dist/runner/opencode/managed-run-controller.js +1 -4
  15. package/dist/runner/opencode-runner.js +2 -2
  16. package/dist/service/continuous-run-coordinator.js +11 -18
  17. package/dist/service/{goal-mcp-server.js → goal-mcp-bridge.js} +19 -14
  18. package/dist/service/goal-mcp-stdio.js +5 -4
  19. package/dist/service/goal-runtime.js +21 -36
  20. package/dist/service/hosted-conversation-node-service.js +2 -0
  21. package/dist/service/mcp-installation-verifier.js +2 -1
  22. package/dist/service/mcp-node-operation-service.js +3 -1
  23. package/dist/service/{mail-notification-mcp-server.js → notification-mcp-bridge.js} +32 -24
  24. package/dist/service/{mail-notification-mcp-stdio.js → notification-mcp-stdio.js} +18 -12
  25. package/dist/service/restart-session-restorer.js +2 -2
  26. package/dist/service/{scheduled-task-mcp-server.js → schedule-mcp-bridge.js} +25 -19
  27. package/dist/service/{scheduled-task-mcp-stdio.js → schedule-mcp-stdio.js} +8 -7
  28. package/dist/service/session-catalog-service.js +3 -3
  29. package/package.json +4 -4
  30. /package/dist/{mcp-installation-model.js → mcp/model.js} +0 -0
@@ -1,16 +1,17 @@
1
+ import { NODE_MCP, NODE_MCP_VERSION } from '../mcp/naming.js';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { connect } from 'node:net';
3
4
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
6
  import { z } from 'zod';
6
7
  import { notificationInputSchema } from '@myagentroam/protocol';
7
- const endpoint = process.env['MAR_MAIL_MCP_ENDPOINT'];
8
- const token = process.env['MAR_MAIL_MCP_TOKEN'];
8
+ const endpoint = process.env['MAR_NOTIFICATION_MCP_ENDPOINT'];
9
+ const token = process.env['MAR_NOTIFICATION_MCP_TOKEN'];
9
10
  if (endpoint === undefined || token === undefined)
10
11
  throw new Error('MAIL_CAPABILITY_INVALID');
11
- const server = new McpServer({ name: 'mar_notification', version: '1.0.0' });
12
- server.registerTool('mar_send_email_notification', {
13
- description: 'Send a notification email to the specified recipient from this MAR-managed Workspace conversation. The Agent works with files and tools on the authorized MAR Node; the MAR Server sends email using its administrator-configured SMTP account. This is not the browser or Node local email account, nor a repository cron/CI notification. This tool cannot access SMTP credentials. Omit the template to use the built-in notification template in the sender’s preferred language; optionally select notification-en-US or notification-zh-CN. Each call may send a real email; do not retry after an uncertain result.',
12
+ const server = new McpServer({ name: NODE_MCP.notification.name, version: NODE_MCP_VERSION });
13
+ server.registerTool(NODE_MCP.notification.tools.email, {
14
+ description: 'Send a notification email from this MAR-managed Workspace conversation. To email the initiating user’s own account, omit recipientEmail; the Server uses that account’s saved email address and fails if no valid address is configured. Specify recipientEmail only to send to another explicitly authorized address. The Agent works with files and tools on the authorized MAR Node; the MAR Server sends email using its administrator-configured SMTP account. This is not the browser or Node local email account, nor a repository cron/CI notification. This tool cannot access SMTP credentials. Omit the template to use the built-in notification template in the sender’s preferred language; optionally select notification-en-US or notification-zh-CN. Each call may send a real email; do not retry after an uncertain result.',
14
15
  inputSchema: {
15
16
  templateId: z
16
17
  .string()
@@ -23,16 +24,21 @@ server.registerTool('mar_send_email_notification', {
23
24
  .regex(/^[^\r\n]*$/u)
24
25
  .optional()
25
26
  .describe('Optional notification title rendered into the email subject.'),
26
- recipientEmail: z.string().min(3).max(320).describe('Single recipient email address.'),
27
+ recipientEmail: z
28
+ .string()
29
+ .min(3)
30
+ .max(320)
31
+ .optional()
32
+ .describe('Optional authorized recipient override. Omit to email the initiating user’s own account.'),
27
33
  message: z.string().min(1).max(4000).describe('Notification text for {{message}}.')
28
34
  }
29
35
  }, async (args) => {
30
36
  const value = await request({ ...args, callId: randomUUID() });
31
37
  return { content: [{ type: 'text', text: JSON.stringify(value) }] };
32
38
  });
33
- if (process.env['MAR_NOTIFICATION_CHANNELS'] === '1') {
34
- server.registerTool('mar_send_notification', {
35
- description: 'Send a real notification from the current authorized Workspace execution. Defaults to push to the initiating user; email requires an explicitly authorized recipientEmail. Channels and registered Push clients are independent. Push may be suppressed while any workbench connection is open or unavailable due to system, browser, or OS settings. Acceptance is not delivery. Do not disclose secrets or send to unauthorized recipients. Each invocation is a new side effect; do not retry after an uncertain or partial result.',
39
+ if (process.env['MAR_NOTIFICATION_MCP_CHANNELS'] === '1') {
40
+ server.registerTool(NODE_MCP.notification.tools.send, {
41
+ description: 'Send a real notification from the current authorized Workspace execution. Defaults to push to the initiating user. To email the user’s own account, include email in channels and omit recipientEmail; the Server uses the initiating account’s saved email address and fails if no valid address is configured. Specify recipientEmail only to send to another explicitly authorized address. Channels and registered Push clients are independent. Push may be suppressed while any workbench connection is open or unavailable due to system, browser, or OS settings. Acceptance is not delivery. Do not disclose secrets or send to unauthorized recipients. Each invocation is a new side effect; do not retry after an uncertain or partial result.',
36
42
  inputSchema: {
37
43
  channels: z
38
44
  .array(z.enum(['push', 'email']))
@@ -50,17 +56,17 @@ if (process.env['MAR_NOTIFICATION_CHANNELS'] === '1') {
50
56
  .min(3)
51
57
  .max(320)
52
58
  .optional()
53
- .describe('Single recipient email address.'),
59
+ .describe('Optional authorized recipient override for the email channel. Omit to email the initiating user’s own account.'),
54
60
  templateId: z.enum(['', 'notification-en-US', 'notification-zh-CN']).optional()
55
61
  }
56
62
  }, async (args) => {
57
63
  const input = notificationInputSchema.parse(args);
58
- const value = await request({ ...input, callId: randomUUID() }, 'mar_send_notification');
64
+ const value = await request({ ...input, callId: randomUUID() }, NODE_MCP.notification.tools.send);
59
65
  return { content: [{ type: 'text', text: JSON.stringify(value) }] };
60
66
  });
61
67
  }
62
68
  await server.connect(new StdioServerTransport());
63
- function request(args, tool = 'mar_send_email_notification') {
69
+ function request(args, tool = NODE_MCP.notification.tools.email) {
64
70
  return new Promise((resolve, reject) => {
65
71
  const socket = connect(endpoint);
66
72
  socket.setTimeout(35_000, () => socket.destroy(new Error('MAIL_DELIVERY_UNCERTAIN')));
@@ -1,4 +1,4 @@
1
- import { serializeComposerTag } from '@myagentroam/protocol';
1
+ import { parseComposerTags, serializeComposerTag } from '@myagentroam/protocol';
2
2
  import { parseMessageAttachments } from '../util/node-operation-parsers.js';
3
3
  import { RESTART_NOTICE } from './planned-restart-handoff.js';
4
4
  /** Rebuild inputs through the existing preparation and FIFO boundary. */
@@ -70,7 +70,7 @@ export class RestartSessionRestorer {
70
70
  sessionId: session.id,
71
71
  content,
72
72
  clientMessageId: input.clientMessageId,
73
- ...(content.startsWith('<mar_restart>')
73
+ ...(parseComposerTags(content)[0]?.kind === 'mar_restart'
74
74
  ? { __runnerContent: `${content}${RESTART_NOTICE}` }
75
75
  : {}),
76
76
  attachments: input.attachments,
@@ -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';
@@ -5,7 +6,7 @@ import { createServer } from 'node:net';
5
6
  import { tmpdir } from 'node:os';
6
7
  import { join } from 'node:path';
7
8
  import { fileURLToPath } from 'node:url';
8
- export class ScheduledTaskMcpServer {
9
+ export class ScheduleMcpBridge {
9
10
  store;
10
11
  changed;
11
12
  proposed;
@@ -24,23 +25,28 @@ export class ScheduledTaskMcpServer {
24
25
  this.create = create;
25
26
  this.resolveSessionId = resolveSessionId;
26
27
  }
27
- async installation(capability) {
28
+ async prepare(capability) {
28
29
  await this.#listen();
30
+ const args = scheduleMcpStdioArgs();
29
31
  const token = randomBytes(32).toString('base64url');
30
- const secretName = `MAR_SCHEDULE_MCP_${randomBytes(16).toString('hex').toUpperCase()}`;
31
32
  this.#capabilities.set(token, capability);
32
33
  return {
33
- installation: {
34
- __marScheduled: true,
35
- serverName: 'mar_schedule',
36
- enabled: true,
37
- command: process.execPath,
38
- args: scheduledMcpStdioArgs(),
39
- endpoint: this.#endpoint,
40
- secretName,
41
- readOnly: capability.readonly
34
+ name: NODE_MCP.schedule.name,
35
+ origin: 'BUILTIN',
36
+ lifecycle: 'EXECUTION',
37
+ transport: 'STDIO',
38
+ command: process.execPath,
39
+ args,
40
+ environment: {
41
+ MAR_SCHEDULE_MCP_ENDPOINT: this.#endpoint,
42
+ MAR_SCHEDULE_MCP_TOKEN: token,
43
+ MAR_SCHEDULE_MCP_READ_ONLY: capability.readonly ? '1' : '0'
42
44
  },
43
- secrets: { [secretName]: token }
45
+ allowedToolCallers: {
46
+ [NODE_MCP.schedule.tools.get]: ['direct', 'programmatic'],
47
+ [NODE_MCP.schedule.tools.create]: ['direct', 'programmatic'],
48
+ [NODE_MCP.schedule.tools.update]: ['direct', 'programmatic']
49
+ }
44
50
  };
45
51
  }
46
52
  revoke(executionId) {
@@ -95,11 +101,11 @@ export class ScheduledTaskMcpServer {
95
101
  (task !== undefined &&
96
102
  (task.id !== capability.taskId || task.revision !== capability.revision)))
97
103
  throw new Error('SCHEDULE_CAPABILITY_STALE');
98
- if (request.tool === 'mar_get_scheduled_task') {
104
+ if (request.tool === NODE_MCP.schedule.tools.get) {
99
105
  socket.end(`${JSON.stringify({ result: task ?? null })}\n`);
100
106
  return;
101
107
  }
102
- if (request.tool === 'mar_create_scheduled_task') {
108
+ if (request.tool === NODE_MCP.schedule.tools.create) {
103
109
  if (capability.readonly)
104
110
  throw new Error('SCHEDULE_READ_ONLY');
105
111
  if (task !== undefined)
@@ -126,7 +132,7 @@ export class ScheduledTaskMcpServer {
126
132
  socket.end(`${JSON.stringify({ result: created })}\n`);
127
133
  return;
128
134
  }
129
- if (request.tool !== 'mar_update_scheduled_task')
135
+ if (request.tool !== NODE_MCP.schedule.tools.update)
130
136
  throw new Error('SCHEDULE_TOOL_INVALID');
131
137
  if (capability.readonly)
132
138
  throw new Error('SCHEDULE_READ_ONLY');
@@ -171,11 +177,11 @@ function record(value) {
171
177
  ? value
172
178
  : {};
173
179
  }
174
- function scheduledMcpStdioArgs() {
175
- const javascript = fileURLToPath(new URL('./scheduled-task-mcp-stdio.js', import.meta.url));
180
+ function scheduleMcpStdioArgs() {
181
+ const javascript = fileURLToPath(new URL('./schedule-mcp-stdio.js', import.meta.url));
176
182
  if (existsSync(javascript))
177
183
  return [javascript];
178
- const typescript = fileURLToPath(new URL('./scheduled-task-mcp-stdio.ts', import.meta.url));
184
+ const typescript = fileURLToPath(new URL('./schedule-mcp-stdio.ts', import.meta.url));
179
185
  if (!existsSync(typescript))
180
186
  throw new Error('SCHEDULE_MCP_STDIO_ENTRY_UNAVAILABLE');
181
187
  return ['--import', import.meta.resolve('tsx'), typescript];
@@ -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,17 +7,17 @@ const endpoint = process.env['MAR_SCHEDULE_MCP_ENDPOINT'];
6
7
  const token = process.env['MAR_SCHEDULE_MCP_TOKEN'];
7
8
  if (endpoint === undefined || token === undefined)
8
9
  throw new Error('SCHEDULE_CAPABILITY_INVALID');
9
- const server = new McpServer({ name: 'mar_schedule', version: '1.0.0' });
10
+ const server = new McpServer({ name: NODE_MCP.schedule.name, version: NODE_MCP_VERSION });
10
11
  const scopeNote = 'Scope is limited to the MAR scheduled task managed by Node for this conversation; ' +
11
12
  'it does not cover repository, CI, system cron, or other timers.';
12
13
  const objectiveDescription = 'Task body for one scheduled run in this conversation. State what that run should do with enough task context. ' +
13
14
  'Do not phrase it as a request to create or change a schedule or repeat cron/time-zone settings; MAR handles scheduling and skipped triggers.';
14
- server.registerTool('mar_get_scheduled_task', {
15
+ server.registerTool(NODE_MCP.schedule.tools.get, {
15
16
  description: 'Read the MAR scheduled task bound to this MAR-managed Workspace conversation, or null if none exists. ' +
16
17
  scopeNote
17
- }, async () => result(await request('mar_get_scheduled_task', {})));
18
+ }, async () => result(await request(NODE_MCP.schedule.tools.get, {})));
18
19
  if (process.env['MAR_SCHEDULE_MCP_READ_ONLY'] !== '1') {
19
- server.registerTool('mar_create_scheduled_task', {
20
+ server.registerTool(NODE_MCP.schedule.tools.create, {
20
21
  description: 'Create the MAR scheduled task for this MAR-managed Workspace conversation. MAR runs it in this conversation for each admitted trigger using the connected Node time zone. ' +
21
22
  scopeNote,
22
23
  inputSchema: {
@@ -27,8 +28,8 @@ if (process.env['MAR_SCHEDULE_MCP_READ_ONLY'] !== '1') {
27
28
  .max(64)
28
29
  .describe('Five-field cron expression interpreted in the connected MAR Node time zone. It defines the schedule separately from the per-run task body.')
29
30
  }
30
- }, async (args) => result(await request('mar_create_scheduled_task', args)));
31
- server.registerTool('mar_update_scheduled_task', {
31
+ }, async (args) => result(await request(NODE_MCP.schedule.tools.create, args)));
32
+ server.registerTool(NODE_MCP.schedule.tools.update, {
32
33
  description: 'Update the MAR scheduled task for this MAR-managed Workspace conversation. MAR runs the saved objective in this conversation for each admitted trigger using the connected Node time zone. ' +
33
34
  scopeNote,
34
35
  inputSchema: {
@@ -40,7 +41,7 @@ if (process.env['MAR_SCHEDULE_MCP_READ_ONLY'] !== '1') {
40
41
  .describe('Replacement five-field cron expression in the connected MAR Node time zone.')
41
42
  .optional()
42
43
  }
43
- }, async (args) => result(await request('mar_update_scheduled_task', args)));
44
+ }, async (args) => result(await request(NODE_MCP.schedule.tools.update, args)));
44
45
  }
45
46
  await server.connect(new StdioServerTransport());
46
47
  function request(tool, args) {
@@ -294,9 +294,9 @@ export class SessionCatalogService {
294
294
  (!this.options.runnerAvailable(session.runner) ||
295
295
  this.options.runtime.getAgentSession(session.id) !== undefined)) ||
296
296
  (!missingCandidate &&
297
- (!Number.isFinite(session.lastActivityAt) ||
298
- session.lastActivityAt <= 0 ||
299
- session.lastActivityAt >= cutoff)) ||
297
+ Number.isFinite(session.lastActivityAt) &&
298
+ session.lastActivityAt > 0 &&
299
+ session.lastActivityAt >= cutoff) ||
300
300
  (session.pinOrder !== null && session.pinOrder !== undefined) ||
301
301
  this.options.isWatching(session.id) ||
302
302
  this.options.isRecovering?.(session.id) ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.10.40",
3
+ "version": "0.10.42",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -37,10 +37,10 @@
37
37
  "playwright": "1.62.1",
38
38
  "ws": "^8.21.3",
39
39
  "zod": "4.4.3",
40
- "@myagentroam/agent": "0.10.40",
40
+ "@myagentroam/agent": "0.10.42",
41
41
  "@myagentroam/code-navigation": "0.10.1",
42
- "@myagentroam/marctl": "0.10.7",
43
- "@myagentroam/protocol": "0.10.40"
42
+ "@myagentroam/protocol": "0.10.42",
43
+ "@myagentroam/marctl": "0.10.7"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/ws": "^8.18.1"