@myagentroam/node 0.10.60 → 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.
@@ -3,13 +3,84 @@ export class ScheduledSessionCoordinator {
3
3
  options;
4
4
  #now;
5
5
  #timer;
6
- #running = false;
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.options.notify(task, occurrenceId, SCHEDULE_RESTART_OUTCOME_UNKNOWN, 'FAILED');
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
- async #tick(initial) {
125
+ #tick(initial) {
54
126
  if (this.#closed || this.#running)
55
127
  return;
56
- this.#running = true;
57
- try {
58
- const now = this.#now();
59
- for (const task of this.options.store().listDue(now)) {
60
- if (this.#closed || task.nextRunAt === null)
61
- break;
62
- const occurrenceId = this.options
63
- .store()
64
- .claim(task.sessionId, task.nextRunAt, task.revision);
65
- if (occurrenceId === undefined)
66
- continue;
67
- let failureCode;
68
- let submitting = false;
69
- try {
70
- if (initial && task.nextRunAt < now)
71
- failureCode = 'SCHEDULE_NODE_RESTARTED';
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
- if (failureCode === undefined) {
75
- const context = await this.options.authorize(task, occurrenceId);
76
- const currentTask = this.options.store().get(task.sessionId);
77
- if (this.#closed)
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
- finally {
104
- this.#running = false;
105
- this.#arm();
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.60",
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.60",
40
+ "@myagentroam/agent": "0.10.62",
41
41
  "@myagentroam/code-navigation": "0.10.1",
42
- "@myagentroam/protocol": "0.10.60",
43
- "@myagentroam/marctl": "0.10.12"
42
+ "@myagentroam/marctl": "0.10.12",
43
+ "@myagentroam/protocol": "0.10.62"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/ws": "^8.18.1"