@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
package/dist/connector.js
CHANGED
|
@@ -66,6 +66,8 @@ import { ScheduledSessionCoordinator } from './service/scheduled-session-coordin
|
|
|
66
66
|
import { ScheduledSessionStore } from './service/scheduled-session-store.js';
|
|
67
67
|
import { ScheduledRunAuthorizationControl } from './service/scheduled-run-authorization-control.js';
|
|
68
68
|
import { ScheduledTaskMcpServer } from './service/scheduled-task-mcp-server.js';
|
|
69
|
+
import { MailNotificationControl } from './service/mail-notification-control.js';
|
|
70
|
+
import { MailNotificationMcpServer, resolveMailNotificationCapability } from './service/mail-notification-mcp-server.js';
|
|
69
71
|
import { SessionCatalogService } from './service/session-catalog-service.js';
|
|
70
72
|
import { SessionCommandService } from './service/session-command-service.js';
|
|
71
73
|
import { SessionPresentationService } from './service/session-presentation-service.js';
|
|
@@ -157,6 +159,7 @@ export class NodeConnector {
|
|
|
157
159
|
controlChannel;
|
|
158
160
|
controlMessages;
|
|
159
161
|
scheduledRunAuthorization;
|
|
162
|
+
mailNotificationControl = new MailNotificationControl((envelope) => this.sendEnvelope(envelope));
|
|
160
163
|
scheduledSessions;
|
|
161
164
|
nodeRequests;
|
|
162
165
|
connectionLifecycle;
|
|
@@ -288,6 +291,7 @@ export class NodeConnector {
|
|
|
288
291
|
throw new Error('SCHEDULE_SERVICE_UNAVAILABLE');
|
|
289
292
|
return this.sessionScheduleService.createFromMcp(input);
|
|
290
293
|
}, (sessionId) => this.runtime.getAgentSession(sessionId)?.externalSessionId ?? sessionId);
|
|
294
|
+
mailNotificationMcpServer = new MailNotificationMcpServer((capability) => resolveMailNotificationCapability(this.runtime, capability), (request) => this.mailNotificationControl.request(request));
|
|
291
295
|
runnerUsageReader;
|
|
292
296
|
terminalManager;
|
|
293
297
|
upgradeCoordinator;
|
|
@@ -343,9 +347,15 @@ export class NodeConnector {
|
|
|
343
347
|
return;
|
|
344
348
|
const store = new ScheduledSessionStore(this.database.raw);
|
|
345
349
|
store.finish(run.clientMessageId, status === 'SUCCEEDED' ? 'SUCCEEDED' : 'FAILED', status === 'SUCCEEDED' ? undefined : `SCHEDULE_RUN_${status}`, run.id);
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (task
|
|
350
|
+
const task = store.getById(run.clientMessageId.split(':', 1)[0]);
|
|
351
|
+
if (task !== undefined) {
|
|
352
|
+
if (task.notifyOnTerminal)
|
|
353
|
+
this.publishScheduledTaskNotification(task, run.clientMessageId, {
|
|
354
|
+
kind: 'SCHEDULE_TERMINAL',
|
|
355
|
+
outcome: status,
|
|
356
|
+
runId: run.id
|
|
357
|
+
});
|
|
358
|
+
else if (status !== 'SUCCEEDED')
|
|
349
359
|
this.emitScheduledNotification(task, run.clientMessageId, `SCHEDULE_RUN_${status}`, 'FAILED');
|
|
350
360
|
}
|
|
351
361
|
},
|
|
@@ -422,6 +432,7 @@ export class NodeConnector {
|
|
|
422
432
|
this.goalExecutionProgress.delete(executionId);
|
|
423
433
|
this.goalMcpServer.revoke(executionId);
|
|
424
434
|
this.scheduledTaskMcpServer.revoke(executionId);
|
|
435
|
+
this.mailNotificationMcpServer.revoke(executionId);
|
|
425
436
|
this.continuousRunCoordinator.remove(executionId);
|
|
426
437
|
}
|
|
427
438
|
});
|
|
@@ -831,6 +842,34 @@ export class NodeConnector {
|
|
|
831
842
|
},
|
|
832
843
|
secrets: resolved.secrets
|
|
833
844
|
};
|
|
845
|
+
},
|
|
846
|
+
mailMcp: async (input) => {
|
|
847
|
+
const run = this.runtime.getRun(input.runId);
|
|
848
|
+
if (run?.initiatedByUserId === undefined)
|
|
849
|
+
return undefined;
|
|
850
|
+
const resolved = await this.mailNotificationMcpServer.installation({
|
|
851
|
+
executionId: input.runId,
|
|
852
|
+
sessionId: input.sessionId,
|
|
853
|
+
workspaceId: input.workspaceId,
|
|
854
|
+
userId: run.initiatedByUserId,
|
|
855
|
+
source: run.source === 'SCHEDULED' ? 'SCHEDULED' : 'MANUAL'
|
|
856
|
+
});
|
|
857
|
+
return {
|
|
858
|
+
installation: {
|
|
859
|
+
targetKind: 'WORKSPACE',
|
|
860
|
+
targetId: input.workspaceId,
|
|
861
|
+
catalogEntryId: 'mar-internal-mail',
|
|
862
|
+
serverName: 'mar_notification',
|
|
863
|
+
displayName: 'MAR email notification',
|
|
864
|
+
version: '1.0.0',
|
|
865
|
+
enabled: true,
|
|
866
|
+
runtime: { transport: 'HTTP', url: 'https://127.0.0.1.invalid/mcp', headers: {} },
|
|
867
|
+
configuration: resolved.installation,
|
|
868
|
+
installedAt: Date.now(),
|
|
869
|
+
...resolved.installation
|
|
870
|
+
},
|
|
871
|
+
secrets: resolved.secrets
|
|
872
|
+
};
|
|
834
873
|
}
|
|
835
874
|
});
|
|
836
875
|
this.workspaceQueueWorkbenchService = new WorkspaceQueueWorkbenchService({
|
|
@@ -1191,6 +1230,8 @@ export class NodeConnector {
|
|
|
1191
1230
|
this.directTransferService?.close();
|
|
1192
1231
|
this.runtimeCredentials.disconnected();
|
|
1193
1232
|
this.scheduledRunAuthorization.disconnected();
|
|
1233
|
+
this.mailNotificationControl.disconnected();
|
|
1234
|
+
this.mailNotificationMcpServer.revokeAll();
|
|
1194
1235
|
this.terminalChannel.disconnect();
|
|
1195
1236
|
if (code === 4000 && reason === 'NODE_CONNECTION_REPLACED')
|
|
1196
1237
|
nodeLog('node.connection.replaced', {
|
|
@@ -1209,6 +1250,7 @@ export class NodeConnector {
|
|
|
1209
1250
|
handleNodeRequest: (envelope) => this.nodeRequests.handle(envelope),
|
|
1210
1251
|
handleRuntimeCredentialResponse: (envelope) => this.runtimeCredentials.handle(envelope),
|
|
1211
1252
|
handleScheduledRunAuthorizeResponse: (envelope) => this.scheduledRunAuthorization.handle(envelope),
|
|
1253
|
+
handleMailNotificationResponse: (envelope) => this.mailNotificationControl.handle(envelope),
|
|
1212
1254
|
handleDirectTransfer: (envelope) => this.handleDirectTransfer(envelope),
|
|
1213
1255
|
handleTerminalDirect: (envelope) => this.handleTerminalDirect(envelope),
|
|
1214
1256
|
reconcileRuns: (envelope) => this.runEventBridge.reconcile(envelope),
|
|
@@ -1271,6 +1313,7 @@ export class NodeConnector {
|
|
|
1271
1313
|
onConnected: () => {
|
|
1272
1314
|
this.runtimeCredentials.connected();
|
|
1273
1315
|
this.scheduledRunAuthorization.connected();
|
|
1316
|
+
this.mailNotificationControl.connected();
|
|
1274
1317
|
this.recentRunProjectionService.publishSnapshot();
|
|
1275
1318
|
}
|
|
1276
1319
|
});
|
|
@@ -1923,6 +1966,8 @@ export class NodeConnector {
|
|
|
1923
1966
|
this.runtimeCredentials.close();
|
|
1924
1967
|
this.scheduledSessions.stop();
|
|
1925
1968
|
this.scheduledRunAuthorization.close();
|
|
1969
|
+
this.mailNotificationControl.close();
|
|
1970
|
+
const mailMcpDrain = this.mailNotificationMcpServer.close();
|
|
1926
1971
|
this.directTransferService?.dispose();
|
|
1927
1972
|
this.directTransferService = undefined;
|
|
1928
1973
|
this.controlChannel.stop();
|
|
@@ -1934,7 +1979,8 @@ export class NodeConnector {
|
|
|
1934
1979
|
terminalDrain,
|
|
1935
1980
|
uploadDrain,
|
|
1936
1981
|
goalMcpDrain,
|
|
1937
|
-
scheduledMcpDrain
|
|
1982
|
+
scheduledMcpDrain,
|
|
1983
|
+
mailMcpDrain
|
|
1938
1984
|
]);
|
|
1939
1985
|
this.stopping = (async () => {
|
|
1940
1986
|
try {
|
|
@@ -1977,24 +2023,32 @@ export class NodeConnector {
|
|
|
1977
2023
|
}
|
|
1978
2024
|
});
|
|
1979
2025
|
};
|
|
1980
|
-
emitScheduledNotification = (task, occurrenceId, code, status) => {
|
|
2026
|
+
emitScheduledNotification = (task, occurrenceId, code, status) => this.publishScheduledTaskNotification(task, occurrenceId, {
|
|
2027
|
+
kind: status === 'FAILED' ? 'SCHEDULE_FAILED' : 'SCHEDULE_SKIPPED',
|
|
2028
|
+
code
|
|
2029
|
+
});
|
|
2030
|
+
publishScheduledTaskNotification = (task, occurrenceId, event) => {
|
|
1981
2031
|
const occurredAt = Date.now();
|
|
2032
|
+
const recipient = (this.database === undefined
|
|
2033
|
+
? undefined
|
|
2034
|
+
: new ScheduledSessionStore(this.database.raw).getById(task.id))?.ownerUserId ?? task.ownerUserId;
|
|
1982
2035
|
const session = this.runtime.getAgentSession(task.sessionId);
|
|
1983
2036
|
this.emitWorkbenchEvent('conversation', {
|
|
1984
2037
|
event: {
|
|
1985
|
-
runId: occurrenceId,
|
|
2038
|
+
runId: event.kind === 'SCHEDULE_TERMINAL' ? event.runId : occurrenceId,
|
|
1986
2039
|
sequence: 1,
|
|
1987
2040
|
eventType: 'notification',
|
|
1988
2041
|
payload: {
|
|
1989
2042
|
targetType: 'WORKSPACE_SESSION',
|
|
1990
|
-
userId:
|
|
2043
|
+
userId: recipient,
|
|
1991
2044
|
workspaceId: task.workspaceId,
|
|
1992
2045
|
sessionId: task.sessionId,
|
|
1993
2046
|
sessionTitle: session?.customTitle ?? session?.runnerTitle ?? task.sessionId,
|
|
1994
|
-
runId: occurrenceId,
|
|
1995
|
-
notificationId:
|
|
1996
|
-
|
|
1997
|
-
|
|
2047
|
+
runId: event.kind === 'SCHEDULE_TERMINAL' ? event.runId : occurrenceId,
|
|
2048
|
+
notificationId: event.kind === 'SCHEDULE_TERMINAL'
|
|
2049
|
+
? `schedule-terminal:${task.id}:${occurrenceId}`
|
|
2050
|
+
: `schedule:${task.id}:${occurrenceId}`,
|
|
2051
|
+
...event,
|
|
1998
2052
|
occurredAt
|
|
1999
2053
|
},
|
|
2000
2054
|
createdAt: occurredAt
|
package/dist/database.js
CHANGED
|
@@ -6,9 +6,14 @@ import { runnerDefaultConfigurationSchema } from '@myagentroam/protocol';
|
|
|
6
6
|
import { mcpCatalogEntrySchema, mcpInstallationConfigurationSchema, mcpRuntimeDescriptorSchema } from '@myagentroam/protocol';
|
|
7
7
|
import { assertNodeSchemaV13, NODE_SCHEMA_V13, NODE_SCHEMA_VERSION_V13 } from './migrations/v013.js';
|
|
8
8
|
import { NODE_SCHEMA_MIGRATION_V14 } from './migrations/v014.js';
|
|
9
|
+
import { NODE_SCHEMA_MIGRATION_V15 } from './migrations/v015.js';
|
|
9
10
|
// Future released migrations must be contiguous, immutable and begin at v14; Demo is excluded.
|
|
10
|
-
export const NODE_SCHEMA_MIGRATIONS = [
|
|
11
|
+
export const NODE_SCHEMA_MIGRATIONS = [
|
|
12
|
+
NODE_SCHEMA_MIGRATION_V14,
|
|
13
|
+
NODE_SCHEMA_MIGRATION_V15
|
|
14
|
+
];
|
|
11
15
|
const LEGACY_NODE_SCHEMA_V12 = NODE_SCHEMA_VERSION_V13 - 1;
|
|
16
|
+
const OBSOLETE_NODE_SCHEMA_V16 = 16;
|
|
12
17
|
export class NodeDatabase {
|
|
13
18
|
now;
|
|
14
19
|
raw;
|
|
@@ -392,6 +397,13 @@ export function migrate(database, catalog = NODE_SCHEMA_MIGRATIONS) {
|
|
|
392
397
|
const migratedVersions = database
|
|
393
398
|
.prepare('SELECT version FROM node_schema_migrations ORDER BY version')
|
|
394
399
|
.all().map((row) => numberValue(row, 'version'));
|
|
400
|
+
if (latest === NODE_SCHEMA_MIGRATION_V15.version &&
|
|
401
|
+
migratedVersions.length === 4 &&
|
|
402
|
+
migratedVersions.every((version, index) => version === NODE_SCHEMA_VERSION_V13 + index) &&
|
|
403
|
+
migratedVersions.at(-1) === OBSOLETE_NODE_SCHEMA_V16) {
|
|
404
|
+
downgradeLegacyRecipientSchema(database);
|
|
405
|
+
migratedVersions.pop();
|
|
406
|
+
}
|
|
395
407
|
const current = migratedVersions.at(-1);
|
|
396
408
|
if (current === undefined ||
|
|
397
409
|
current < NODE_SCHEMA_VERSION_V13 ||
|
|
@@ -421,6 +433,49 @@ export function migrate(database, catalog = NODE_SCHEMA_MIGRATIONS) {
|
|
|
421
433
|
if (catalog.length > 0)
|
|
422
434
|
catalog[catalog.length - 1].assert(database);
|
|
423
435
|
}
|
|
436
|
+
function downgradeLegacyRecipientSchema(database) {
|
|
437
|
+
const reference = new DatabaseSync(':memory:');
|
|
438
|
+
try {
|
|
439
|
+
migrate(reference, [NODE_SCHEMA_MIGRATION_V14]);
|
|
440
|
+
reference.exec(`ALTER TABLE scheduled_session_tasks
|
|
441
|
+
ADD COLUMN notify_user_id INTEGER NOT NULL DEFAULT 1 CHECK (notify_user_id > 0);
|
|
442
|
+
${NODE_SCHEMA_MIGRATION_V15.sql}`);
|
|
443
|
+
const definitions = (source) => new Map(source
|
|
444
|
+
.prepare('SELECT type,name,sql FROM sqlite_master WHERE sql IS NOT NULL ORDER BY type,name')
|
|
445
|
+
.all()
|
|
446
|
+
.map((row) => {
|
|
447
|
+
const type = row['type'];
|
|
448
|
+
const name = row['name'];
|
|
449
|
+
const sql = row['sql'];
|
|
450
|
+
if (typeof type !== 'string' || typeof name !== 'string' || typeof sql !== 'string')
|
|
451
|
+
throw new Error('NODE_DATABASE_VERSION_UNSUPPORTED');
|
|
452
|
+
return [`${type}:${name}`, sql.replace(/\s+/gu, ' ').trim()];
|
|
453
|
+
}));
|
|
454
|
+
const expected = definitions(reference);
|
|
455
|
+
const actual = definitions(database);
|
|
456
|
+
if (expected.size !== actual.size ||
|
|
457
|
+
[...expected].some(([name, sql]) => actual.get(name) !== sql))
|
|
458
|
+
throw new Error('NODE_DATABASE_VERSION_UNSUPPORTED');
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
reference.close();
|
|
462
|
+
}
|
|
463
|
+
database.exec('BEGIN IMMEDIATE');
|
|
464
|
+
try {
|
|
465
|
+
if (database.prepare('SELECT 1 FROM scheduled_session_tasks LIMIT 1').get() !== undefined)
|
|
466
|
+
throw new Error('NODE_DATABASE_DOWNGRADE_UNSAFE');
|
|
467
|
+
database.exec('ALTER TABLE scheduled_session_tasks DROP COLUMN notify_user_id');
|
|
468
|
+
NODE_SCHEMA_MIGRATION_V15.assert(database);
|
|
469
|
+
database
|
|
470
|
+
.prepare('DELETE FROM node_schema_migrations WHERE version=?')
|
|
471
|
+
.run(OBSOLETE_NODE_SCHEMA_V16);
|
|
472
|
+
database.exec('COMMIT');
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
database.exec('ROLLBACK');
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
424
479
|
function mcpInstallation(row) {
|
|
425
480
|
const runtime = mcpRuntimeDescriptorSchema.parse(JSON.parse(stringValue(row, 'runtime')));
|
|
426
481
|
const configuration = mcpInstallationConfigurationSchema.parse(JSON.parse(stringValue(row, 'configuration')));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NODE_SCHEMA_MIGRATION_V14 } from './v014.js';
|
|
2
|
+
export const NODE_SCHEMA_MIGRATION_V15 = {
|
|
3
|
+
version: 15,
|
|
4
|
+
sql: `ALTER TABLE scheduled_session_tasks
|
|
5
|
+
ADD COLUMN notify_on_terminal INTEGER NOT NULL DEFAULT 0 CHECK (notify_on_terminal IN (0,1));`,
|
|
6
|
+
assert(database) {
|
|
7
|
+
NODE_SCHEMA_MIGRATION_V14.assert(database);
|
|
8
|
+
const columns = new Set(database.prepare('PRAGMA table_info(scheduled_session_tasks)').all().map((column) => column.name));
|
|
9
|
+
if (!columns.has('notify_on_terminal') || columns.has('notify_user_id'))
|
|
10
|
+
throw new Error('NODE_DATABASE_SCHEMA_INVALID');
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -173,7 +173,9 @@ function resolvedMcpServers(raw, secrets) {
|
|
|
173
173
|
const record = value;
|
|
174
174
|
if (typeof record['serverName'] !== 'string' || record['enabled'] !== true)
|
|
175
175
|
continue;
|
|
176
|
-
if ((record['__marGoal'] === true ||
|
|
176
|
+
if ((record['__marGoal'] === true ||
|
|
177
|
+
record['__marScheduled'] === true ||
|
|
178
|
+
record['__marMail'] === true) &&
|
|
177
179
|
typeof record['command'] === 'string' &&
|
|
178
180
|
Array.isArray(record['args']) &&
|
|
179
181
|
record['args'].every((value) => typeof value === 'string') &&
|
|
@@ -191,11 +193,16 @@ function resolvedMcpServers(raw, secrets) {
|
|
|
191
193
|
MAR_GOAL_MCP_ENDPOINT: record['endpoint'],
|
|
192
194
|
MAR_GOAL_MCP_TOKEN: secrets[record['secretName']]
|
|
193
195
|
}
|
|
194
|
-
:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
: record['__marScheduled'] === true
|
|
197
|
+
? {
|
|
198
|
+
MAR_SCHEDULE_MCP_ENDPOINT: record['endpoint'],
|
|
199
|
+
MAR_SCHEDULE_MCP_TOKEN: secrets[record['secretName']],
|
|
200
|
+
MAR_SCHEDULE_MCP_READ_ONLY: record['readOnly'] === true ? '1' : '0'
|
|
201
|
+
}
|
|
202
|
+
: {
|
|
203
|
+
MAR_MAIL_MCP_ENDPOINT: record['endpoint'],
|
|
204
|
+
MAR_MAIL_MCP_TOKEN: secrets[record['secretName']]
|
|
205
|
+
}
|
|
199
206
|
});
|
|
200
207
|
continue;
|
|
201
208
|
}
|