@myagentroam/node 0.9.126 → 0.9.127
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 +65 -11
- package/dist/database.js +56 -1
- package/dist/migrations/v015.js +12 -0
- package/dist/runner/abstract-runner.js +13 -6
- package/dist/runner/mar-agent/computer-use-indicator.js +484 -0
- package/dist/runner/mar-agent/computer-use.js +54 -26
- package/dist/service/continuous-run-coordinator.js +12 -3
- package/dist/service/mail-notification-control.js +69 -0
- package/dist/service/mail-notification-mcp-server.js +150 -0
- package/dist/service/mail-notification-mcp-stdio.js +56 -0
- package/dist/service/mcp-node-operation-service.js +7 -1
- package/dist/service/node-control-message-service.js +2 -0
- package/dist/service/scheduled-session-store.js +13 -6
- package/dist/service/scheduled-task-mcp-server.js +4 -1
- package/dist/service/session-schedule-service.js +6 -1
- package/package.json +4 -4
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createEnvelope, mailNotificationRequestSchema, mailNotificationResponseSchema } from '@myagentroam/protocol';
|
|
2
|
+
export class MailNotificationControl {
|
|
3
|
+
sendEnvelope;
|
|
4
|
+
pending = new Map();
|
|
5
|
+
generation = 0;
|
|
6
|
+
connectedToServer = false;
|
|
7
|
+
closed = false;
|
|
8
|
+
constructor(sendEnvelope) {
|
|
9
|
+
this.sendEnvelope = sendEnvelope;
|
|
10
|
+
}
|
|
11
|
+
connected() {
|
|
12
|
+
this.disconnected();
|
|
13
|
+
this.connectedToServer = true;
|
|
14
|
+
}
|
|
15
|
+
disconnected() {
|
|
16
|
+
this.connectedToServer = false;
|
|
17
|
+
this.generation++;
|
|
18
|
+
for (const pending of [...this.pending.values()])
|
|
19
|
+
pending.reject(new Error('MAIL_DELIVERY_UNCERTAIN'));
|
|
20
|
+
}
|
|
21
|
+
close() {
|
|
22
|
+
this.closed = true;
|
|
23
|
+
this.disconnected();
|
|
24
|
+
}
|
|
25
|
+
handle(envelope) {
|
|
26
|
+
if (envelope.type !== 'mail.notification.response')
|
|
27
|
+
return false;
|
|
28
|
+
const pending = envelope.replyTo === undefined ? undefined : this.pending.get(envelope.replyTo);
|
|
29
|
+
if (pending === undefined || pending.generation !== this.generation)
|
|
30
|
+
return true;
|
|
31
|
+
const parsed = mailNotificationResponseSchema.safeParse(envelope.payload);
|
|
32
|
+
if (!parsed.success)
|
|
33
|
+
pending.reject(new Error('MAIL_RESPONSE_INVALID'));
|
|
34
|
+
else if ('error' in parsed.data)
|
|
35
|
+
pending.reject(new Error(parsed.data.error.code));
|
|
36
|
+
else
|
|
37
|
+
pending.resolve();
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
request(input) {
|
|
41
|
+
if (!this.connectedToServer || this.closed)
|
|
42
|
+
return Promise.reject(new Error('MAIL_SERVER_UNAVAILABLE'));
|
|
43
|
+
const envelope = createEnvelope('mail.notification.send', mailNotificationRequestSchema.parse(input));
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const settle = (action) => {
|
|
46
|
+
const pending = this.pending.get(envelope.id);
|
|
47
|
+
if (pending === undefined)
|
|
48
|
+
return;
|
|
49
|
+
clearTimeout(pending.timer);
|
|
50
|
+
this.pending.delete(envelope.id);
|
|
51
|
+
action();
|
|
52
|
+
};
|
|
53
|
+
const timer = setTimeout(() => settle(() => reject(new Error('MAIL_DELIVERY_UNCERTAIN'))), 30_000);
|
|
54
|
+
timer.unref();
|
|
55
|
+
this.pending.set(envelope.id, {
|
|
56
|
+
generation: this.generation,
|
|
57
|
+
resolve: () => settle(resolve),
|
|
58
|
+
reject: (error) => settle(() => reject(error)),
|
|
59
|
+
timer
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
this.sendEnvelope(envelope);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
settle(() => reject(new Error('MAIL_SERVER_UNAVAILABLE')));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { unlink } from 'node:fs/promises';
|
|
4
|
+
import { createServer } from 'node:net';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { mailNotificationRequestSchema } from '@myagentroam/protocol';
|
|
9
|
+
export class MailNotificationMcpServer {
|
|
10
|
+
resolve;
|
|
11
|
+
send;
|
|
12
|
+
capabilities = new Map();
|
|
13
|
+
sockets = new Set();
|
|
14
|
+
endpoint = process.platform === 'win32'
|
|
15
|
+
? `\\\\.\\pipe\\mar-mail-${process.pid}-${randomUUID()}`
|
|
16
|
+
: join(tmpdir(), `mar-mail-${process.pid}-${randomUUID()}.sock`);
|
|
17
|
+
listening;
|
|
18
|
+
constructor(resolve, send) {
|
|
19
|
+
this.resolve = resolve;
|
|
20
|
+
this.send = send;
|
|
21
|
+
}
|
|
22
|
+
async installation(capability) {
|
|
23
|
+
await this.listen();
|
|
24
|
+
const token = randomBytes(32).toString('base64url');
|
|
25
|
+
const secretName = `MAR_MAIL_MCP_${randomBytes(16).toString('hex').toUpperCase()}`;
|
|
26
|
+
this.capabilities.set(token, capability);
|
|
27
|
+
return {
|
|
28
|
+
installation: {
|
|
29
|
+
__marMail: true,
|
|
30
|
+
serverName: 'mar_notification',
|
|
31
|
+
enabled: true,
|
|
32
|
+
command: process.execPath,
|
|
33
|
+
args: mailMcpStdioArgs(),
|
|
34
|
+
endpoint: this.endpoint,
|
|
35
|
+
secretName
|
|
36
|
+
},
|
|
37
|
+
secrets: { [secretName]: token }
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
revoke(executionId) {
|
|
41
|
+
for (const [token, capability] of this.capabilities)
|
|
42
|
+
if (capability.executionId === executionId)
|
|
43
|
+
this.capabilities.delete(token);
|
|
44
|
+
}
|
|
45
|
+
revokeAll() {
|
|
46
|
+
this.capabilities.clear();
|
|
47
|
+
}
|
|
48
|
+
async close() {
|
|
49
|
+
if (this.listening === undefined)
|
|
50
|
+
return;
|
|
51
|
+
const server = await this.listening;
|
|
52
|
+
this.revokeAll();
|
|
53
|
+
const closed = new Promise((resolve, reject) => server.close((error) => (error === undefined ? resolve() : reject(error))));
|
|
54
|
+
for (const socket of this.sockets)
|
|
55
|
+
socket.destroy();
|
|
56
|
+
await closed;
|
|
57
|
+
if (process.platform !== 'win32')
|
|
58
|
+
await unlink(this.endpoint).catch(() => undefined);
|
|
59
|
+
}
|
|
60
|
+
listen() {
|
|
61
|
+
this.listening ??= new Promise((resolve, reject) => {
|
|
62
|
+
const server = createServer((socket) => this.handle(socket));
|
|
63
|
+
server.once('error', reject);
|
|
64
|
+
server.listen(this.endpoint, () => resolve(server));
|
|
65
|
+
});
|
|
66
|
+
return this.listening;
|
|
67
|
+
}
|
|
68
|
+
handle(socket) {
|
|
69
|
+
this.sockets.add(socket);
|
|
70
|
+
socket.once('close', () => this.sockets.delete(socket));
|
|
71
|
+
let body = '';
|
|
72
|
+
let received = false;
|
|
73
|
+
socket.setEncoding('utf8');
|
|
74
|
+
socket.on('data', (chunk) => {
|
|
75
|
+
if (received)
|
|
76
|
+
return;
|
|
77
|
+
body += chunk;
|
|
78
|
+
if (body.length > 16_000) {
|
|
79
|
+
socket.destroy();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (body.includes('\n')) {
|
|
83
|
+
received = true;
|
|
84
|
+
void this.respond(socket, body.slice(0, body.indexOf('\n')));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async respond(socket, body) {
|
|
89
|
+
try {
|
|
90
|
+
const request = JSON.parse(body);
|
|
91
|
+
const capability = typeof request['token'] === 'string' ? this.capabilities.get(request['token']) : undefined;
|
|
92
|
+
if (capability === undefined)
|
|
93
|
+
throw new Error('MAIL_CAPABILITY_INVALID');
|
|
94
|
+
const current = this.resolve(capability);
|
|
95
|
+
if (current === undefined)
|
|
96
|
+
throw new Error('MAIL_CAPABILITY_STALE');
|
|
97
|
+
if (request['tool'] !== 'mar_send_email_notification')
|
|
98
|
+
throw new Error('MAIL_TOOL_INVALID');
|
|
99
|
+
const args = request['arguments'];
|
|
100
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args))
|
|
101
|
+
throw new Error('MAIL_INPUT_INVALID');
|
|
102
|
+
const { templateId, recipientEmail, message, callId } = args;
|
|
103
|
+
if ((templateId !== undefined && typeof templateId !== 'string') ||
|
|
104
|
+
typeof recipientEmail !== 'string' ||
|
|
105
|
+
typeof message !== 'string' ||
|
|
106
|
+
typeof callId !== 'string')
|
|
107
|
+
throw new Error('MAIL_INPUT_INVALID');
|
|
108
|
+
const parsed = mailNotificationRequestSchema.safeParse({
|
|
109
|
+
...current,
|
|
110
|
+
templateId: templateId ?? '',
|
|
111
|
+
recipientEmail,
|
|
112
|
+
message,
|
|
113
|
+
callId
|
|
114
|
+
});
|
|
115
|
+
if (!parsed.success)
|
|
116
|
+
throw new Error('MAIL_INPUT_INVALID');
|
|
117
|
+
await this.send(parsed.data);
|
|
118
|
+
socket.end(`${JSON.stringify({ result: { accepted: true } })}\n`);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const code = error instanceof Error && /^MAIL_[A-Z0-9_]+$/u.test(error.message)
|
|
122
|
+
? error.message
|
|
123
|
+
: 'MAIL_DELIVERY_UNCERTAIN';
|
|
124
|
+
socket.end(`${JSON.stringify({ error: code })}\n`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export function resolveMailNotificationCapability(runtime, capability) {
|
|
129
|
+
const run = runtime.getRun(capability.executionId);
|
|
130
|
+
const session = runtime.getAgentSession(capability.sessionId);
|
|
131
|
+
if (run === undefined ||
|
|
132
|
+
session === undefined ||
|
|
133
|
+
['SUCCEEDED', 'FAILED', 'INTERRUPTED'].includes(run.status) ||
|
|
134
|
+
runtime.currentExecutionId(run.id) !== capability.executionId ||
|
|
135
|
+
run.sessionId !== session.id ||
|
|
136
|
+
run.workspaceId !== capability.workspaceId ||
|
|
137
|
+
run.initiatedByUserId !== capability.userId ||
|
|
138
|
+
(run.source === 'SCHEDULED' ? 'SCHEDULED' : 'MANUAL') !== capability.source)
|
|
139
|
+
return undefined;
|
|
140
|
+
return { ...capability, sessionId: session.id };
|
|
141
|
+
}
|
|
142
|
+
function mailMcpStdioArgs() {
|
|
143
|
+
const javascript = fileURLToPath(new URL('./mail-notification-mcp-stdio.js', import.meta.url));
|
|
144
|
+
if (existsSync(javascript))
|
|
145
|
+
return [javascript];
|
|
146
|
+
const typescript = fileURLToPath(new URL('./mail-notification-mcp-stdio.ts', import.meta.url));
|
|
147
|
+
if (!existsSync(typescript))
|
|
148
|
+
throw new Error('MAIL_MCP_STDIO_ENTRY_UNAVAILABLE');
|
|
149
|
+
return ['--import', import.meta.resolve('tsx'), typescript];
|
|
150
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { connect } from 'node:net';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
const endpoint = process.env['MAR_MAIL_MCP_ENDPOINT'];
|
|
7
|
+
const token = process.env['MAR_MAIL_MCP_TOKEN'];
|
|
8
|
+
if (endpoint === undefined || token === undefined)
|
|
9
|
+
throw new Error('MAIL_CAPABILITY_INVALID');
|
|
10
|
+
const server = new McpServer({ name: 'mar_notification', version: '1.0.0' });
|
|
11
|
+
server.registerTool('mar_send_email_notification', {
|
|
12
|
+
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.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
templateId: z
|
|
15
|
+
.string()
|
|
16
|
+
.max(64)
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Optional built-in NOTIFICATION template ID; omitted or empty uses the sender’s preferred language.'),
|
|
19
|
+
recipientEmail: z.string().min(3).max(320).describe('Single recipient email address.'),
|
|
20
|
+
message: z.string().min(1).max(4000).describe('Notification text for {{message}}.')
|
|
21
|
+
}
|
|
22
|
+
}, async (args) => {
|
|
23
|
+
const value = await request({ ...args, callId: randomUUID() });
|
|
24
|
+
return { content: [{ type: 'text', text: JSON.stringify(value) }] };
|
|
25
|
+
});
|
|
26
|
+
await server.connect(new StdioServerTransport());
|
|
27
|
+
function request(args) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const socket = connect(endpoint);
|
|
30
|
+
let body = '';
|
|
31
|
+
socket.setEncoding('utf8');
|
|
32
|
+
socket.once('connect', () => socket.write(`${JSON.stringify({ token, tool: 'mar_send_email_notification', arguments: args })}\n`));
|
|
33
|
+
socket.on('data', (chunk) => {
|
|
34
|
+
body += chunk;
|
|
35
|
+
if (body.length > 16_000)
|
|
36
|
+
socket.destroy(new Error('MAIL_RESPONSE_INVALID'));
|
|
37
|
+
});
|
|
38
|
+
socket.once('error', reject);
|
|
39
|
+
socket.once('end', () => {
|
|
40
|
+
try {
|
|
41
|
+
if (body.length === 0)
|
|
42
|
+
throw new Error('MAIL_DELIVERY_UNCERTAIN');
|
|
43
|
+
const payload = JSON.parse(body);
|
|
44
|
+
if (payload.error !== undefined)
|
|
45
|
+
reject(new Error(payload.error));
|
|
46
|
+
else
|
|
47
|
+
resolve(payload.result);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
reject(error instanceof Error && error.message === 'MAIL_DELIVERY_UNCERTAIN'
|
|
51
|
+
? error
|
|
52
|
+
: new Error('MAIL_RESPONSE_INVALID'));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -39,7 +39,13 @@ export class McpNodeOperationService {
|
|
|
39
39
|
status: 'CURRENT',
|
|
40
40
|
installedAt: installation.installedAt
|
|
41
41
|
}));
|
|
42
|
-
return mcpTargetInspectionSchema.parse({
|
|
42
|
+
return mcpTargetInspectionSchema.parse({
|
|
43
|
+
targetKind,
|
|
44
|
+
installations,
|
|
45
|
+
...(targetKind === 'WORKSPACE'
|
|
46
|
+
? { executionBuiltIns: ['mar_schedule', 'mar_notification'] }
|
|
47
|
+
: {})
|
|
48
|
+
});
|
|
43
49
|
}
|
|
44
50
|
async install(targetKind, targetId, raw) {
|
|
45
51
|
if (!isRecord(raw))
|
|
@@ -37,6 +37,8 @@ export class NodeControlMessageService {
|
|
|
37
37
|
return;
|
|
38
38
|
if (this.options.handleScheduledRunAuthorizeResponse?.(envelope) === true)
|
|
39
39
|
return;
|
|
40
|
+
if (this.options.handleMailNotificationResponse?.(envelope) === true)
|
|
41
|
+
return;
|
|
40
42
|
if (envelope.type.startsWith('direct.') &&
|
|
41
43
|
this.options.handleDirectTransfer?.(envelope) === true)
|
|
42
44
|
return;
|
|
@@ -40,6 +40,7 @@ export class ScheduledSessionStore {
|
|
|
40
40
|
workspaceId: input.workspaceId,
|
|
41
41
|
runner: input.runner,
|
|
42
42
|
ownerUserId: input.ownerUserId,
|
|
43
|
+
notifyOnTerminal: input.notifyOnTerminal ?? false,
|
|
43
44
|
cron: input.cron,
|
|
44
45
|
timezone: input.timezone,
|
|
45
46
|
objective: input.objective.trim(),
|
|
@@ -52,9 +53,9 @@ export class ScheduledSessionStore {
|
|
|
52
53
|
};
|
|
53
54
|
this.database
|
|
54
55
|
.prepare(`INSERT INTO scheduled_session_tasks
|
|
55
|
-
(id,session_id,workspace_id,runner,owner_user_id,objective,cron,timezone,enabled,revision,next_run_at,created_at,updated_at)
|
|
56
|
-
VALUES (
|
|
57
|
-
.run(task.id, task.sessionId, task.workspaceId, task.runner, task.ownerUserId, task.objective, task.cron, task.timezone, task.enabled ? 1 : 0, 1, task.nextRunAt, now, now);
|
|
56
|
+
(id,session_id,workspace_id,runner,owner_user_id,notify_on_terminal,objective,cron,timezone,enabled,revision,next_run_at,created_at,updated_at)
|
|
57
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`)
|
|
58
|
+
.run(task.id, task.sessionId, task.workspaceId, task.runner, task.ownerUserId, task.notifyOnTerminal ? 1 : 0, task.objective, task.cron, task.timezone, task.enabled ? 1 : 0, 1, task.nextRunAt, now, now);
|
|
58
59
|
return task;
|
|
59
60
|
}
|
|
60
61
|
update(sessionId, expectedRevision, patch) {
|
|
@@ -63,11 +64,15 @@ export class ScheduledSessionStore {
|
|
|
63
64
|
throw new Error('SCHEDULE_NOT_FOUND');
|
|
64
65
|
if (current.revision !== expectedRevision)
|
|
65
66
|
throw new Error('SCHEDULE_REVISION_CONFLICT');
|
|
67
|
+
if (patch.ownerUserId !== undefined &&
|
|
68
|
+
(!Number.isSafeInteger(patch.ownerUserId) || patch.ownerUserId < 1))
|
|
69
|
+
throw new Error('SCHEDULE_OWNER_INVALID');
|
|
66
70
|
const objective = patch.objective ?? current.objective;
|
|
67
71
|
const cron = patch.cron ?? current.cron;
|
|
68
72
|
const timezone = patch.timezone ?? current.timezone;
|
|
69
73
|
validate(objective, cron, timezone);
|
|
70
74
|
const enabled = patch.enabled ?? current.enabled;
|
|
75
|
+
const ownerUserId = patch.ownerUserId ?? current.ownerUserId;
|
|
71
76
|
const nextRunAt = enabled
|
|
72
77
|
? patch.enabled === true || patch.cron !== undefined || patch.timezone !== undefined
|
|
73
78
|
? nextScheduledAt(cron, timezone, this.now())
|
|
@@ -75,9 +80,9 @@ export class ScheduledSessionStore {
|
|
|
75
80
|
: null;
|
|
76
81
|
const now = this.now();
|
|
77
82
|
const result = this.database
|
|
78
|
-
.prepare(`UPDATE scheduled_session_tasks SET objective=?,cron=?,timezone=?,enabled=?,revision=?,next_run_at=?,updated_at=?
|
|
83
|
+
.prepare(`UPDATE scheduled_session_tasks SET objective=?,cron=?,timezone=?,enabled=?,owner_user_id=?,notify_on_terminal=?,revision=?,next_run_at=?,updated_at=?
|
|
79
84
|
WHERE session_id=? AND revision=?`)
|
|
80
|
-
.run(objective.trim(), cron, timezone, enabled ? 1 : 0, expectedRevision + 1, nextRunAt, now, sessionId, expectedRevision);
|
|
85
|
+
.run(objective.trim(), cron, timezone, enabled ? 1 : 0, ownerUserId, (patch.notifyOnTerminal ?? current.notifyOnTerminal) ? 1 : 0, expectedRevision + 1, nextRunAt, now, sessionId, expectedRevision);
|
|
81
86
|
if (result.changes !== 1)
|
|
82
87
|
throw new Error('SCHEDULE_REVISION_CONFLICT');
|
|
83
88
|
this.database
|
|
@@ -193,7 +198,8 @@ export class ScheduledSessionStore {
|
|
|
193
198
|
? {}
|
|
194
199
|
: { objective: proposal['objective'] }),
|
|
195
200
|
...(proposal['cron'] === null ? {} : { cron: proposal['cron'] }),
|
|
196
|
-
...(proposal['timezone'] === null ? {} : { timezone: proposal['timezone'] })
|
|
201
|
+
...(proposal['timezone'] === null ? {} : { timezone: proposal['timezone'] }),
|
|
202
|
+
ownerUserId
|
|
197
203
|
})
|
|
198
204
|
: task;
|
|
199
205
|
this.database
|
|
@@ -222,6 +228,7 @@ function taskFromRow(row) {
|
|
|
222
228
|
workspaceId: row['workspace_id'],
|
|
223
229
|
runner: row['runner'],
|
|
224
230
|
ownerUserId: Number(row['owner_user_id']),
|
|
231
|
+
notifyOnTerminal: row['notify_on_terminal'] === 1,
|
|
225
232
|
objective: row['objective'],
|
|
226
233
|
cron: row['cron'],
|
|
227
234
|
timezone: row['timezone'],
|
|
@@ -144,7 +144,10 @@ export class ScheduledTaskMcpServer {
|
|
|
144
144
|
...(cron === undefined ? {} : { cron: cron })
|
|
145
145
|
};
|
|
146
146
|
if (capability.actorUserId === task.ownerUserId) {
|
|
147
|
-
const updated = this.store().update(task.sessionId, task.revision,
|
|
147
|
+
const updated = this.store().update(task.sessionId, task.revision, {
|
|
148
|
+
...patch,
|
|
149
|
+
ownerUserId: capability.actorUserId
|
|
150
|
+
});
|
|
148
151
|
this.#capabilities.set(request.token, {
|
|
149
152
|
...capability,
|
|
150
153
|
revision: updated.revision
|
|
@@ -48,12 +48,14 @@ export class SessionScheduleService {
|
|
|
48
48
|
const current = store.get(sessionId);
|
|
49
49
|
const objective = input.objective;
|
|
50
50
|
const cron = input.cron;
|
|
51
|
+
const notifyOnTerminal = input.notifyOnTerminal;
|
|
51
52
|
const timezone = current === undefined ? nodeTimezone() : undefined;
|
|
52
53
|
if ((objective !== undefined &&
|
|
53
54
|
(typeof objective !== 'string' ||
|
|
54
55
|
objective.trim().length < 1 ||
|
|
55
56
|
objective.trim().length > 20_000)) ||
|
|
56
57
|
(cron !== undefined && typeof cron !== 'string') ||
|
|
58
|
+
(notifyOnTerminal !== undefined && typeof notifyOnTerminal !== 'boolean') ||
|
|
57
59
|
input.timezone !== undefined ||
|
|
58
60
|
(input.enabled !== undefined && typeof input.enabled !== 'boolean'))
|
|
59
61
|
throw new Error('SCHEDULE_INVALID');
|
|
@@ -82,6 +84,7 @@ export class SessionScheduleService {
|
|
|
82
84
|
workspaceId: session.workspaceId,
|
|
83
85
|
runner: session.runner,
|
|
84
86
|
ownerUserId: actor,
|
|
87
|
+
...(notifyOnTerminal === undefined ? {} : { notifyOnTerminal }),
|
|
85
88
|
objective: objective,
|
|
86
89
|
cron: cron,
|
|
87
90
|
timezone: timezone,
|
|
@@ -91,7 +94,9 @@ export class SessionScheduleService {
|
|
|
91
94
|
...(objective === undefined ? {} : { objective: objective }),
|
|
92
95
|
...(cron === undefined ? {} : { cron: cron }),
|
|
93
96
|
...(timezone === undefined ? {} : { timezone: timezone }),
|
|
94
|
-
...(input.enabled === undefined ? {} : { enabled: input.enabled })
|
|
97
|
+
...(input.enabled === undefined ? {} : { enabled: input.enabled }),
|
|
98
|
+
ownerUserId: actor,
|
|
99
|
+
...(notifyOnTerminal === undefined ? {} : { notifyOnTerminal })
|
|
95
100
|
});
|
|
96
101
|
const updated = this.options.runtime.getAgentSession(session.id) ?? session;
|
|
97
102
|
this.options.emitSession(updated);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.127",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"npm": "11.12.1",
|
|
31
31
|
"ws": "^8.21.3",
|
|
32
32
|
"zod": "4.4.3",
|
|
33
|
-
"@myagentroam/agent": "0.9.
|
|
34
|
-
"@myagentroam/
|
|
35
|
-
"@myagentroam/
|
|
33
|
+
"@myagentroam/agent": "0.9.127",
|
|
34
|
+
"@myagentroam/code-navigation": "0.9.127",
|
|
35
|
+
"@myagentroam/protocol": "0.9.127"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/ws": "^8.18.1"
|