@myagentroam/node 0.12.4 → 0.12.6

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.
@@ -1,8 +1,14 @@
1
1
  import { nodeLog } from '../operational.js';
2
2
  const WATCH_INTERVAL_MS = 30_000;
3
3
  const CACHE_TTL_MS = 15_000;
4
+ /** Owns native discovery caches, periodic observation and internal page/discovery admission.
5
+ * Cancels owned discovery generations before joining reads; Runner execution remains with the Runner.
6
+ */
4
7
  export class WorkspaceSessionService {
5
8
  options;
9
+ accepting = true;
10
+ pageReads = new Set();
11
+ discoveryReads = new Map();
6
12
  cacheGenerations = new Map();
7
13
  initialReads = new Map();
8
14
  incompleteDiscoveries = new Set();
@@ -15,10 +21,37 @@ export class WorkspaceSessionService {
15
21
  constructor(options) {
16
22
  this.options = options;
17
23
  }
24
+ async listPage(workspaceId, sessionGroup) {
25
+ if (!this.accepting)
26
+ throw new Error('NODE_STOPPED');
27
+ const read = this.options.listPage(workspaceId, sessionGroup);
28
+ this.pageReads.add(read);
29
+ try {
30
+ return await read;
31
+ }
32
+ finally {
33
+ this.pageReads.delete(read);
34
+ }
35
+ }
36
+ resume() {
37
+ this.accepting = true;
38
+ }
39
+ async stop() {
40
+ this.accepting = false;
41
+ for (const controller of this.discoveryReads.values())
42
+ controller.abort(new Error('NODE_STOPPED'));
43
+ for (const id of this.sessionWatchTimers.keys())
44
+ this.stopWatch(id);
45
+ await Promise.allSettled([...this.pageReads, ...this.sessionWatchReads.values()]);
46
+ // Retired cache generations also remain owned until their cancellable projections settle.
47
+ await Promise.allSettled([...this.discoveryReads.keys()]);
48
+ }
18
49
  cachedSnapshot(workspaceId) {
19
50
  return this.sessionCache.get(workspaceId)?.sessions ?? [];
20
51
  }
21
52
  ensureWatch(workspaceId) {
53
+ if (!this.accepting)
54
+ return;
22
55
  if (this.sessionWatchTimers.has(workspaceId) || this.sessionWatchReads.has(workspaceId))
23
56
  return;
24
57
  void this.runWatchCycle(workspaceId);
@@ -35,11 +68,15 @@ export class WorkspaceSessionService {
35
68
  return this.incompleteDiscoveries.has(workspaceId);
36
69
  }
37
70
  async cached(workspaceId) {
71
+ if (!this.accepting)
72
+ throw new Error('NODE_STOPPED');
38
73
  const cached = this.sessionCache.get(workspaceId);
39
74
  if (cached !== undefined && cached.expiresAt > Date.now())
40
75
  return cached.sessions;
41
76
  let discovery = this.sessionCacheReads.get(workspaceId);
42
77
  if (discovery === undefined) {
78
+ const controller = new AbortController();
79
+ const signal = controller.signal;
43
80
  // A retired read must not regain ownership when its Workspace state is reclaimed.
44
81
  const generation = Symbol();
45
82
  this.cacheGenerations.set(workspaceId, generation);
@@ -59,6 +96,8 @@ export class WorkspaceSessionService {
59
96
  let batchPublished = false;
60
97
  let notifyCompletion = false;
61
98
  const publish = (sessions, notify = true) => {
99
+ if (signal.aborted)
100
+ return;
62
101
  if (this.cacheGenerations.get(workspaceId) !== generation)
63
102
  return;
64
103
  const previous = this.sessionCache.get(workspaceId)?.sessions;
@@ -66,13 +105,16 @@ export class WorkspaceSessionService {
66
105
  sessions,
67
106
  expiresAt: this.staleDiscoveries.has(workspaceId) ? 0 : Date.now() + CACHE_TTL_MS
68
107
  });
69
- if (notify &&
108
+ if (this.accepting &&
109
+ notify &&
70
110
  previous !== undefined &&
71
111
  sessionSignature(previous) !== sessionSignature(sessions))
72
112
  this.options.invalidate(workspaceId);
73
113
  };
74
114
  const read = this.options
75
115
  .discover(workspaceId, (runner, sessions) => {
116
+ if (signal.aborted)
117
+ return;
76
118
  if (!acceptingBatches || this.cacheGenerations.get(workspaceId) !== generation)
77
119
  return;
78
120
  const previous = this.sessionCache.get(workspaceId)?.sessions;
@@ -86,8 +128,9 @@ export class WorkspaceSessionService {
86
128
  publish(merged);
87
129
  batchPublished = true;
88
130
  resolveInitial(merged);
89
- })
131
+ }, signal)
90
132
  .then((sessions) => {
133
+ signal.throwIfAborted();
91
134
  if (this.cacheGenerations.get(workspaceId) === generation)
92
135
  this.incompleteDiscoveries.delete(workspaceId);
93
136
  const previous = this.sessionCache.get(workspaceId)?.sessions;
@@ -100,7 +143,7 @@ export class WorkspaceSessionService {
100
143
  })
101
144
  .catch((error) => {
102
145
  rejectInitial(error);
103
- if (this.cacheGenerations.get(workspaceId) === generation) {
146
+ if (!signal.aborted && this.cacheGenerations.get(workspaceId) === generation) {
104
147
  nodeLog('native.session.discovery.failed', { workspaceId });
105
148
  const partial = this.sessionCache.get(workspaceId);
106
149
  if (partial !== undefined)
@@ -109,6 +152,7 @@ export class WorkspaceSessionService {
109
152
  throw error;
110
153
  })
111
154
  .finally(() => {
155
+ this.discoveryReads.delete(read);
112
156
  acceptingBatches = false;
113
157
  if (this.sessionCacheReads.get(workspaceId) === read) {
114
158
  this.sessionCacheReads.delete(workspaceId);
@@ -118,7 +162,10 @@ export class WorkspaceSessionService {
118
162
  }
119
163
  if (this.initialReads.get(workspaceId) === initial)
120
164
  this.initialReads.delete(workspaceId);
121
- if (notifyCompletion && this.cacheGenerations.get(workspaceId) === generation)
165
+ if (this.accepting &&
166
+ !signal.aborted &&
167
+ notifyCompletion &&
168
+ this.cacheGenerations.get(workspaceId) === generation)
122
169
  this.options.invalidate(workspaceId);
123
170
  if (!this.sessionCacheReads.has(workspaceId) &&
124
171
  !this.sessionCache.has(workspaceId) &&
@@ -126,6 +173,7 @@ export class WorkspaceSessionService {
126
173
  this.cacheGenerations.delete(workspaceId);
127
174
  });
128
175
  discovery = read;
176
+ this.discoveryReads.set(read, controller);
129
177
  this.sessionCacheReads.set(workspaceId, discovery);
130
178
  // Later Runner failures cannot become unhandled after the first batch was returned.
131
179
  void discovery.catch(() => undefined);
@@ -176,7 +224,9 @@ export class WorkspaceSessionService {
176
224
  }
177
225
  async runWatchCycle(workspaceId) {
178
226
  await this.refreshWatch(workspaceId);
179
- if (!this.options.active(workspaceId) || this.sessionWatchTimers.has(workspaceId))
227
+ if (!this.accepting ||
228
+ !this.options.active(workspaceId) ||
229
+ this.sessionWatchTimers.has(workspaceId))
180
230
  return;
181
231
  const timer = setTimeout(() => {
182
232
  this.sessionWatchTimers.delete(workspaceId);
@@ -186,11 +236,11 @@ export class WorkspaceSessionService {
186
236
  this.sessionWatchTimers.set(workspaceId, timer);
187
237
  }
188
238
  async performRefresh(workspaceId) {
189
- if (!this.options.active(workspaceId))
239
+ if (!this.accepting || !this.options.active(workspaceId))
190
240
  return this.stopWatch(workspaceId);
191
241
  try {
192
- const signature = JSON.stringify(await this.options.listPage(workspaceId));
193
- if (!this.options.active(workspaceId))
242
+ const signature = JSON.stringify(await this.listPage(workspaceId));
243
+ if (!this.accepting || !this.options.active(workspaceId))
194
244
  return this.stopWatch(workspaceId);
195
245
  const previous = this.sessionWatchSignatures.get(workspaceId);
196
246
  this.sessionWatchSignatures.set(workspaceId, signature);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.12.4",
3
+ "version": "0.12.6",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -42,13 +42,13 @@
42
42
  "turndown-plugin-gfm": "^1.0.2",
43
43
  "ws": "^8.21.3",
44
44
  "zod": "4.4.3",
45
- "@myagentroam/agent": "0.12.4",
46
- "@myagentroam/protocol": "0.12.4"
45
+ "@myagentroam/agent": "0.12.6",
46
+ "@myagentroam/protocol": "0.12.6"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/turndown": "^5.0.6",
50
50
  "@types/ws": "^8.18.1",
51
- "@myagentroam/marctl": "0.12.4",
51
+ "@myagentroam/marctl": "0.12.6",
52
52
  "@myagentroam/code-navigation": "0.11.1"
53
53
  },
54
54
  "exports": {