@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
|
@@ -3,13 +3,84 @@ export class ScheduledSessionCoordinator {
|
|
|
3
3
|
options;
|
|
4
4
|
#now;
|
|
5
5
|
#timer;
|
|
6
|
-
#running
|
|
6
|
+
#running;
|
|
7
7
|
#closed = true;
|
|
8
8
|
#recovered = [];
|
|
9
9
|
constructor(options) {
|
|
10
10
|
this.options = options;
|
|
11
11
|
this.#now = options.now ?? Date.now;
|
|
12
12
|
}
|
|
13
|
+
executionTerminated(run, status) {
|
|
14
|
+
if (run.source !== 'SCHEDULED')
|
|
15
|
+
return;
|
|
16
|
+
const store = this.options.store();
|
|
17
|
+
store.finish(run.clientMessageId, status === 'SUCCEEDED' ? 'SUCCEEDED' : 'FAILED', status === 'SUCCEEDED' ? undefined : `SCHEDULE_RUN_${status}`, run.id);
|
|
18
|
+
const task = store.getById(run.clientMessageId.split(':', 1)[0]);
|
|
19
|
+
if (task !== undefined) {
|
|
20
|
+
if (task.notifyOnTerminal)
|
|
21
|
+
this.publishTaskNotification(task, run.clientMessageId, {
|
|
22
|
+
kind: 'SCHEDULE_TERMINAL',
|
|
23
|
+
outcome: status,
|
|
24
|
+
runId: run.id
|
|
25
|
+
});
|
|
26
|
+
else if (status !== 'SUCCEEDED')
|
|
27
|
+
this.notify(task, run.clientMessageId, `SCHEDULE_RUN_${status}`, 'FAILED');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
notify = (task, occurrenceId, code, status) => this.publishTaskNotification(task, occurrenceId, {
|
|
31
|
+
kind: status === 'FAILED' ? 'SCHEDULE_FAILED' : 'SCHEDULE_SKIPPED',
|
|
32
|
+
code
|
|
33
|
+
});
|
|
34
|
+
publishTaskNotification = (task, occurrenceId, event) => {
|
|
35
|
+
const occurredAt = Date.now();
|
|
36
|
+
const recipient = this.options.store().getById(task.id)?.ownerUserId ?? task.ownerUserId;
|
|
37
|
+
this.options.emitNotification({
|
|
38
|
+
event: {
|
|
39
|
+
runId: event.kind === 'SCHEDULE_TERMINAL' ? event.runId : occurrenceId,
|
|
40
|
+
sequence: 1,
|
|
41
|
+
eventType: 'notification',
|
|
42
|
+
payload: {
|
|
43
|
+
targetType: 'WORKSPACE_SESSION',
|
|
44
|
+
userId: recipient,
|
|
45
|
+
workspaceId: task.workspaceId,
|
|
46
|
+
sessionId: task.sessionId,
|
|
47
|
+
sessionTitle: this.options.sessionTitle(task.sessionId),
|
|
48
|
+
runId: event.kind === 'SCHEDULE_TERMINAL' ? event.runId : occurrenceId,
|
|
49
|
+
notificationId: event.kind === 'SCHEDULE_TERMINAL'
|
|
50
|
+
? `schedule-terminal:${task.id}:${occurrenceId}`
|
|
51
|
+
: `schedule:${task.id}:${occurrenceId}`,
|
|
52
|
+
...event,
|
|
53
|
+
occurredAt
|
|
54
|
+
},
|
|
55
|
+
createdAt: occurredAt
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
proposalCreated = (sessionId, proposalId) => {
|
|
60
|
+
const task = this.options.store().get(sessionId);
|
|
61
|
+
if (task === undefined)
|
|
62
|
+
return;
|
|
63
|
+
const occurredAt = Date.now();
|
|
64
|
+
this.options.emitNotification({
|
|
65
|
+
event: {
|
|
66
|
+
runId: proposalId,
|
|
67
|
+
sequence: 1,
|
|
68
|
+
eventType: 'notification',
|
|
69
|
+
payload: {
|
|
70
|
+
targetType: 'WORKSPACE_SESSION',
|
|
71
|
+
userId: task.ownerUserId,
|
|
72
|
+
workspaceId: task.workspaceId,
|
|
73
|
+
sessionId,
|
|
74
|
+
sessionTitle: this.options.sessionTitle(sessionId),
|
|
75
|
+
runId: proposalId,
|
|
76
|
+
notificationId: `schedule-proposal:${task.id}:${proposalId}`,
|
|
77
|
+
kind: 'SCHEDULE_PROPOSAL',
|
|
78
|
+
occurredAt
|
|
79
|
+
},
|
|
80
|
+
createdAt: occurredAt
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
};
|
|
13
84
|
/** Called only at Node startup, before admitting any new scheduled occurrence. */
|
|
14
85
|
recoverInterrupted() {
|
|
15
86
|
for (const recovered of this.options.store().recoverInterrupted())
|
|
@@ -18,7 +89,7 @@ export class ScheduledSessionCoordinator {
|
|
|
18
89
|
connected() {
|
|
19
90
|
// Startup has no control connection yet. Send these best-effort alerts once it is ready.
|
|
20
91
|
for (const { task, occurrenceId } of this.#recovered.splice(0)) {
|
|
21
|
-
this.
|
|
92
|
+
this.notify(task, occurrenceId, SCHEDULE_RESTART_OUTCOME_UNKNOWN, 'FAILED');
|
|
22
93
|
}
|
|
23
94
|
}
|
|
24
95
|
start() {
|
|
@@ -36,6 +107,7 @@ export class ScheduledSessionCoordinator {
|
|
|
36
107
|
if (this.#timer !== undefined)
|
|
37
108
|
clearTimeout(this.#timer);
|
|
38
109
|
this.#timer = undefined;
|
|
110
|
+
return this.#running;
|
|
39
111
|
}
|
|
40
112
|
#arm() {
|
|
41
113
|
if (this.#timer !== undefined)
|
|
@@ -50,59 +122,58 @@ export class ScheduledSessionCoordinator {
|
|
|
50
122
|
this.#timer = setTimeout(() => void this.#tick(false), delay);
|
|
51
123
|
this.#timer.unref();
|
|
52
124
|
}
|
|
53
|
-
|
|
125
|
+
#tick(initial) {
|
|
54
126
|
if (this.#closed || this.#running)
|
|
55
127
|
return;
|
|
56
|
-
this.#running =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
128
|
+
this.#running = this.#runDue(initial).finally(() => {
|
|
129
|
+
this.#running = undefined;
|
|
130
|
+
this.#arm();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async #runDue(initial) {
|
|
134
|
+
const now = this.#now();
|
|
135
|
+
for (const task of this.options.store().listDue(now)) {
|
|
136
|
+
if (this.#closed || task.nextRunAt === null)
|
|
137
|
+
break;
|
|
138
|
+
const occurrenceId = this.options
|
|
139
|
+
.store()
|
|
140
|
+
.claim(task.sessionId, task.nextRunAt, task.revision);
|
|
141
|
+
if (occurrenceId === undefined)
|
|
142
|
+
continue;
|
|
143
|
+
let failureCode;
|
|
144
|
+
let submitting = false;
|
|
145
|
+
try {
|
|
146
|
+
if (initial && task.nextRunAt < now)
|
|
147
|
+
failureCode = 'SCHEDULE_NODE_RESTARTED';
|
|
148
|
+
else if (this.options.busy(task))
|
|
149
|
+
failureCode = 'SCHEDULE_SESSION_BUSY';
|
|
150
|
+
if (failureCode === undefined) {
|
|
151
|
+
const context = await this.options.authorize(task, occurrenceId);
|
|
152
|
+
const currentTask = this.options.store().get(task.sessionId);
|
|
153
|
+
if (this.#closed)
|
|
154
|
+
failureCode = 'SCHEDULE_NODE_STOPPED';
|
|
155
|
+
else if (currentTask?.revision !== task.revision || currentTask.enabled !== true)
|
|
156
|
+
failureCode = 'SCHEDULE_TASK_CHANGED';
|
|
72
157
|
else if (this.options.busy(task))
|
|
73
158
|
failureCode = 'SCHEDULE_SESSION_BUSY';
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
failureCode = 'SCHEDULE_NODE_STOPPED';
|
|
79
|
-
else if (currentTask?.revision !== task.revision || currentTask.enabled !== true)
|
|
80
|
-
failureCode = 'SCHEDULE_TASK_CHANGED';
|
|
81
|
-
else if (this.options.busy(task))
|
|
82
|
-
failureCode = 'SCHEDULE_SESSION_BUSY';
|
|
83
|
-
else {
|
|
84
|
-
submitting = true;
|
|
85
|
-
const submitted = await this.options.submit(task, occurrenceId, context);
|
|
86
|
-
this.options.store().finish(occurrenceId, 'SUBMITTED', undefined, submitted.runId);
|
|
87
|
-
}
|
|
159
|
+
else {
|
|
160
|
+
submitting = true;
|
|
161
|
+
const submitted = await this.options.submit(task, occurrenceId, context);
|
|
162
|
+
this.options.store().finish(occurrenceId, 'SUBMITTED', undefined, submitted.runId);
|
|
88
163
|
}
|
|
89
164
|
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
failureCode =
|
|
92
|
-
error instanceof Error && /^[A-Z][A-Z0-9_]{0,127}$/.test(error.message)
|
|
93
|
-
? error.message
|
|
94
|
-
: 'SCHEDULE_EXECUTION_FAILED';
|
|
95
|
-
}
|
|
96
|
-
if (failureCode !== undefined) {
|
|
97
|
-
const status = submitting && failureCode !== 'SCHEDULE_SESSION_BUSY' ? 'FAILED' : 'SKIPPED';
|
|
98
|
-
this.options.store().finish(occurrenceId, status, failureCode);
|
|
99
|
-
this.options.notify(task, occurrenceId, failureCode, status);
|
|
100
|
-
}
|
|
101
165
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
166
|
+
catch (error) {
|
|
167
|
+
failureCode =
|
|
168
|
+
error instanceof Error && /^[A-Z][A-Z0-9_]{0,127}$/.test(error.message)
|
|
169
|
+
? error.message
|
|
170
|
+
: 'SCHEDULE_EXECUTION_FAILED';
|
|
171
|
+
}
|
|
172
|
+
if (failureCode !== undefined) {
|
|
173
|
+
const status = submitting && failureCode !== 'SCHEDULE_SESSION_BUSY' ? 'FAILED' : 'SKIPPED';
|
|
174
|
+
this.options.store().finish(occurrenceId, status, failureCode);
|
|
175
|
+
this.notify(task, occurrenceId, failureCode, status);
|
|
176
|
+
}
|
|
106
177
|
}
|
|
107
178
|
}
|
|
108
179
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.62",
|
|
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.62",
|
|
40
41
|
"@myagentroam/code-navigation": "0.10.1",
|
|
41
42
|
"@myagentroam/marctl": "0.10.12",
|
|
42
|
-
"@myagentroam/
|
|
43
|
-
"@myagentroam/protocol": "0.10.61"
|
|
43
|
+
"@myagentroam/protocol": "0.10.62"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/ws": "^8.18.1"
|