@myagentroam/node 0.11.17 → 0.11.19

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.
@@ -60,6 +60,7 @@ export function unavailableCapabilities() {
60
60
  promptCompletion: { apiVersion: 1, contextVersion: 3, automaticTriggers: true },
61
61
  chatgptLogin: { apiVersion: 1, loopback: true },
62
62
  workspaceDirectTransfer: workspaceDirectTransferCapability(),
63
+ sessionGroups: { apiVersion: 1 },
63
64
  terminalDirect: terminalDirectCapability()
64
65
  };
65
66
  }
@@ -96,6 +97,7 @@ export function detectCapabilities(commands = {}) {
96
97
  promptCompletion: { apiVersion: 1, contextVersion: 3, automaticTriggers: true },
97
98
  chatgptLogin: { apiVersion: 1, loopback: true },
98
99
  workspaceDirectTransfer: workspaceDirectTransferCapability(),
100
+ sessionGroups: { apiVersion: 1 },
99
101
  terminalDirect: terminalDirectCapability()
100
102
  };
101
103
  }
@@ -136,6 +138,7 @@ export async function detectCapabilitiesAsync(commands = {}) {
136
138
  promptCompletion: { apiVersion: 1, contextVersion: 3, automaticTriggers: true },
137
139
  chatgptLogin: { apiVersion: 1, loopback: true },
138
140
  workspaceDirectTransfer: workspaceDirectTransferCapability(),
141
+ sessionGroups: { apiVersion: 1 },
139
142
  terminalDirect: terminalDirectCapability()
140
143
  };
141
144
  }
package/dist/connector.js CHANGED
@@ -46,6 +46,7 @@ import { WorkspaceSessionService } from './service/workspace-session-service.js'
46
46
  import { WorkbenchEventService } from './service/workbench-event-service.js';
47
47
  import { RunDomainState } from './service/run-domain-state.js';
48
48
  import { RecentRunProjectionService } from './service/recent-run-projection-service.js';
49
+ import { sessionGroupChangedSchema } from '@myagentroam/protocol';
49
50
  import { SessionDomainState } from './service/session-domain-state.js';
50
51
  import { RunEventService } from './service/run-event-service.js';
51
52
  import { RunAttachmentService } from './service/run-attachment-service.js';
@@ -79,7 +80,7 @@ import { MailNotificationControl } from './service/mail-notification-control.js'
79
80
  import { NotificationMcpBridge, resolveNotificationCapability } from './service/notification-mcp-bridge.js';
80
81
  import { SessionCatalogService } from './service/session-catalog-service.js';
81
82
  import { SessionCommandService } from './service/session-command-service.js';
82
- import { SessionPresentationService } from './service/session-presentation-service.js';
83
+ import { SessionPresentationService, sessionGroup } from './service/session-presentation-service.js';
83
84
  import { isPlainRecord, isRunnerImageAttachment, parseRunnerImageAttachments } from './util/node-operation-parsers.js';
84
85
  import { WebToolService } from './service/web-tool-service.js';
85
86
  export { nodeConnectEndpoint, validatedCapabilities } from './util/node-operation-parsers.js';
@@ -172,6 +173,7 @@ export class NodeConnector {
172
173
  restartAuthorization;
173
174
  restartHandoff;
174
175
  inFlightOperations = new Set();
176
+ pendingSessionGroups = new Map();
175
177
  mailNotificationControl = new MailNotificationControl((envelope) => this.sendEnvelope(envelope));
176
178
  scheduledSessions;
177
179
  nodeRequests;
@@ -907,6 +909,17 @@ export class NodeConnector {
907
909
  emitTurn: (turn) => this.emitConversationTurn(turn)
908
910
  });
909
911
  this.sessionLifecycleService = new SessionLifecycleService({
912
+ parentCleared: (session) => {
913
+ this.recentRunProjectionService.clearSessionParent(session.workspaceId, session.id);
914
+ const change = {
915
+ nodeId: session.nodeId,
916
+ workspaceId: session.workspaceId,
917
+ sessionId: session.id,
918
+ sessionGroup: 'MAIN'
919
+ };
920
+ this.pendingSessionGroups.set(session.id, change);
921
+ this.send('session.group.changed', change);
922
+ },
910
923
  database: () => {
911
924
  if (this.database === undefined)
912
925
  throw new Error('NODE_DATABASE_UNAVAILABLE');
@@ -1176,8 +1189,19 @@ export class NodeConnector {
1176
1189
  await this.connectionLifecycle.register(nodeId, credential);
1177
1190
  this.mailNotificationControl.setFeatures(payload);
1178
1191
  this.usageOutbox?.connected(payload);
1192
+ for (const change of this.pendingSessionGroups.values())
1193
+ this.send('session.group.changed', change);
1179
1194
  },
1180
1195
  usageAck: (payload) => this.usageOutbox?.handle(createEnvelope('usage.ack', payload)),
1196
+ sessionGroupAck: (payload) => {
1197
+ const parsed = sessionGroupChangedSchema.safeParse(payload);
1198
+ if (!parsed.success)
1199
+ return;
1200
+ const pending = this.pendingSessionGroups.get(parsed.data.sessionId);
1201
+ if (pending?.nodeId === parsed.data.nodeId &&
1202
+ pending.workspaceId === parsed.data.workspaceId)
1203
+ this.pendingSessionGroups.delete(parsed.data.sessionId);
1204
+ },
1181
1205
  bindUsage: (payload) => {
1182
1206
  if (!isPlainRecord(payload) ||
1183
1207
  typeof payload.runId !== 'string' ||
@@ -1189,6 +1213,8 @@ export class NodeConnector {
1189
1213
  },
1190
1214
  connected: (payload) => {
1191
1215
  this.usageOutbox?.connected(payload);
1216
+ for (const change of this.pendingSessionGroups.values())
1217
+ this.send('session.group.changed', change);
1192
1218
  this.connectionLifecycle.connected();
1193
1219
  this.mailNotificationControl.setFeatures(payload);
1194
1220
  },
@@ -1326,6 +1352,9 @@ export class NodeConnector {
1326
1352
  const session = this.runtime.getAgentSession(sessionId);
1327
1353
  return session?.customTitle ?? session?.runnerTitle ?? sessionId;
1328
1354
  },
1355
+ sessionGroup: (task) => sessionGroup(this.runtime.getAgentSession(task.sessionId) ??
1356
+ this.database?.getNativeSessionMetadata(task.runner, task.sessionId, task.workspaceId) ??
1357
+ {}),
1329
1358
  emitNotification: (payload) => this.emitWorkbenchEvent('conversation', payload)
1330
1359
  });
1331
1360
  this.runnerService = new RunnerService(requireDatabase, () => this.capabilities, this.runners, this.runnerUsageReader);
@@ -1357,7 +1386,11 @@ export class NodeConnector {
1357
1386
  files: this.workspaceFileService,
1358
1387
  uploads: this.workspaceUploadService,
1359
1388
  changes: this.workspaceChangeService,
1360
- listSessions: (workspaceId) => this.executeNodeOperation('session.list', { workspaceId, limit: 30 }),
1389
+ listSessions: (workspaceId, sessionGroup) => this.executeNodeOperation('session.list', {
1390
+ workspaceId,
1391
+ limit: 30,
1392
+ ...(sessionGroup === undefined ? {} : { sessionGroup })
1393
+ }),
1361
1394
  invalidate: this.invalidateWorkspaceFiles,
1362
1395
  nodeId: () => this.config?.nodeId ?? 'runtime-node',
1363
1396
  nodeGeneration: () => this.nodeGeneration,
package/dist/database.js CHANGED
@@ -281,6 +281,13 @@ export class NodeDatabase {
281
281
  pinOrder: nullableNumber(row, 'pin_order')
282
282
  };
283
283
  }
284
+ clearSessionParent(runner, externalSessionId, workspaceId) {
285
+ const result = this.raw
286
+ .prepare("UPDATE agent_sessions SET metadata = json_set(metadata, '$.parentSessionId', NULL) WHERE id = ? AND runner = ? AND workspace_id = ?")
287
+ .run(externalSessionId, runner, workspaceId);
288
+ if (result.changes !== 1)
289
+ throw new Error('SESSION_NOT_FOUND');
290
+ }
284
291
  saveNativeSessionMetadata(input) {
285
292
  const current = this.getNativeSessionMetadata(input.runner, input.externalSessionId, input.workspaceId);
286
293
  const customTitle = input.customTitle === undefined
@@ -308,7 +315,7 @@ export class NodeDatabase {
308
315
  const model = input.model === undefined ? (current?.model ?? null) : input.model;
309
316
  const effort = input.effort === undefined ? (current?.effort ?? null) : input.effort;
310
317
  const access = input.access === undefined ? (current?.access ?? null) : input.access;
311
- const parentSessionId = current?.parentSessionId ?? input.parentSessionId ?? null;
318
+ const parentSessionId = current === undefined ? (input.parentSessionId ?? null) : (current.parentSessionId ?? null);
312
319
  const metadata = JSON.stringify({
313
320
  version: 1,
314
321
  parentSessionId,
@@ -29,20 +29,23 @@ export class MarAgentMcpClientCache {
29
29
  let entry = this.#entries.get(key);
30
30
  if (entry === undefined) {
31
31
  await this.#evictIdleEntry();
32
- if (this.#entries.size >= this.#maxEntries) {
33
- const pool = await MarAgentMcpClientPool.connect(servers, cwd, options);
34
- return singleUseLease(pool);
32
+ entry = this.#entries.get(key);
33
+ if (entry === undefined) {
34
+ if (this.#entries.size >= this.#maxEntries) {
35
+ const pool = await MarAgentMcpClientPool.connect(servers, cwd, options);
36
+ return singleUseLease(pool);
37
+ }
38
+ entry = {
39
+ promise: MarAgentMcpClientPool.connect(servers, cwd, options),
40
+ references: 0,
41
+ lastReleasedAt: 0
42
+ };
43
+ this.#entries.set(key, entry);
44
+ entry.promise.catch(() => {
45
+ if (this.#entries.get(key) === entry)
46
+ this.#entries.delete(key);
47
+ });
35
48
  }
36
- entry = {
37
- promise: MarAgentMcpClientPool.connect(servers, cwd, options),
38
- references: 0,
39
- lastReleasedAt: 0
40
- };
41
- this.#entries.set(key, entry);
42
- entry.promise.catch(() => {
43
- if (this.#entries.get(key) === entry)
44
- this.#entries.delete(key);
45
- });
46
49
  }
47
50
  if (entry.timer !== undefined) {
48
51
  clearTimeout(entry.timer);
@@ -343,6 +343,9 @@ export class NodeRuntimeState {
343
343
  titleSource: title === null ? 'AUTO' : 'CUSTOM'
344
344
  });
345
345
  }
346
+ clearSessionParent(sessionId) {
347
+ return this.replaceSession(this.requireSession(sessionId).id, { parentSessionId: null });
348
+ }
346
349
  /**
347
350
  * Give a new or restored managed Session a readable local title while the
348
351
  * Runner still has a placeholder. Never replace a user rename or a
@@ -51,6 +51,10 @@ export class NodeControlMessageService {
51
51
  }
52
52
  if (envelope.type === 'ack')
53
53
  return;
54
+ if (envelope.type === 'session.group.ack') {
55
+ this.options.sessionGroupAck?.(envelope.payload);
56
+ return;
57
+ }
54
58
  if (envelope.type === 'run.approval.decision') {
55
59
  this.decideApproval(envelope);
56
60
  return;
@@ -7,6 +7,7 @@ import { tmpdir } from 'node:os';
7
7
  import { join } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { notificationInputSchema, notificationChannelsRequestSchema, mailNotificationRequestSchema } from '@myagentroam/protocol';
10
+ import { sessionGroup } from './session-presentation-service.js';
10
11
  export class NotificationMcpBridge {
11
12
  resolve;
12
13
  send;
@@ -177,7 +178,7 @@ export function resolveNotificationCapability(runtime, capability) {
177
178
  run.initiatedByUserId !== capability.userId ||
178
179
  (run.source === 'SCHEDULED' ? 'SCHEDULED' : 'MANUAL') !== capability.source)
179
180
  return undefined;
180
- return { ...capability, sessionId: session.id };
181
+ return { ...capability, sessionId: session.id, sessionGroup: sessionGroup(session) };
181
182
  }
182
183
  function notificationMcpStdioArgs() {
183
184
  const javascript = fileURLToPath(new URL('./notification-mcp-stdio.js', import.meta.url));
@@ -38,7 +38,8 @@ export class PromptCompletionService {
38
38
  finally {
39
39
  const context = JSON.stringify(analysis);
40
40
  try {
41
- await this.options.record?.({
41
+ // Keep persistence on the journal's serial queue, outside the response path.
42
+ const recording = this.options.record?.({
42
43
  schemaVersion: 1,
43
44
  startedAt,
44
45
  finishedAt: this.now(),
@@ -63,6 +64,9 @@ export class PromptCompletionService {
63
64
  status: failed ? 'failed' : result?.status,
64
65
  reason: failed ? failureCode : result?.status === 'skipped' ? result.reason : undefined
65
66
  });
67
+ void recording?.catch(() => {
68
+ console.warn('PROMPT_COMPLETION_JOURNAL_WRITE_FAILED');
69
+ });
66
70
  }
67
71
  catch {
68
72
  console.warn('PROMPT_COMPLETION_JOURNAL_WRITE_FAILED');
@@ -1,3 +1,4 @@
1
+ import { sessionGroup } from './session-presentation-service.js';
1
2
  import { composerVisibleText } from '@myagentroam/protocol';
2
3
  const TERMINAL_TTL_MS = 12 * 60 * 60_000;
3
4
  const CLEANUP_INTERVAL_MS = 5 * 60_000;
@@ -36,6 +37,7 @@ export class RecentRunProjectionService {
36
37
  workspaceId: run.workspaceId,
37
38
  sessionId: run.sessionId,
38
39
  sessionTitle: session.customTitle ?? session.runnerTitle ?? session.id,
40
+ sessionGroup: sessionGroup(session),
39
41
  ...(latestUserMessage === undefined ? {} : { userMessage: latestUserMessage }),
40
42
  runner: run.runner,
41
43
  status: run.status,
@@ -55,6 +57,11 @@ export class RecentRunProjectionService {
55
57
  this.cleanup();
56
58
  return [...this.runsBySession.values()].sort(compareRecentRuns);
57
59
  }
60
+ clearSessionParent(workspaceId, sessionId) {
61
+ for (const [key, run] of this.runsBySession)
62
+ if (run.workspaceId === workspaceId && run.sessionId === sessionId)
63
+ this.runsBySession.set(key, { ...run, sessionGroup: 'MAIN' });
64
+ }
58
65
  publishSnapshot() {
59
66
  this.emit({ kind: 'SNAPSHOT', runs: [...this.snapshot()] });
60
67
  }
@@ -1,3 +1,4 @@
1
+ import { sessionGroup } from './session-presentation-service.js';
1
2
  import { permissionStopSchema, runPerformanceSchema } from '@myagentroam/protocol';
2
3
  import { nodeLog } from '../operational.js';
3
4
  import { isPlainRecord, isTerminalRunStatus } from '../util/node-operation-parsers.js';
@@ -252,6 +253,7 @@ export class RunEventService {
252
253
  workspaceId: run.workspaceId,
253
254
  sessionId: run.sessionId,
254
255
  sessionTitle: session.customTitle ?? session.runnerTitle ?? session.id,
256
+ sessionGroup: sessionGroup(session),
255
257
  runId
256
258
  };
257
259
  if (!Number.isSafeInteger(base.userId))
@@ -41,6 +41,7 @@ export class ScheduledSessionCoordinator {
41
41
  eventType: 'notification',
42
42
  payload: {
43
43
  targetType: 'WORKSPACE_SESSION',
44
+ sessionGroup: this.options.sessionGroup?.(task) ?? 'MAIN',
44
45
  userId: currentTask.ownerUserId,
45
46
  workspaceId: task.workspaceId,
46
47
  sessionId: task.sessionId,
@@ -68,6 +69,7 @@ export class ScheduledSessionCoordinator {
68
69
  eventType: 'notification',
69
70
  payload: {
70
71
  targetType: 'WORKSPACE_SESSION',
72
+ sessionGroup: this.options.sessionGroup?.(task) ?? 'MAIN',
71
73
  userId: task.ownerUserId,
72
74
  workspaceId: task.workspaceId,
73
75
  sessionId,
@@ -1,3 +1,4 @@
1
+ import { sessionGroup } from './session-presentation-service.js';
1
2
  import { removeNativeSession } from '../native-session-history.js';
2
3
  import { nodeLog } from '../operational.js';
3
4
  import { isTerminalRunStatus, sessionPage } from '../util/node-operation-parsers.js';
@@ -20,15 +21,33 @@ export class SessionCatalogService {
20
21
  };
21
22
  }
22
23
  async list(input) {
24
+ if (input.sessionGroup !== undefined &&
25
+ input.sessionGroup !== 'MAIN' &&
26
+ input.sessionGroup !== 'CHILD')
27
+ throw new Error('SESSION_INVALID');
23
28
  const sessions = await this.collect(input);
24
- const page = sessionPage(sessions.filter((session) => input.parentSessionId === undefined || session.parentSessionId === input.parentSessionId), input);
29
+ const runningCounts = { MAIN: 0, CHILD: 0 };
30
+ for (const session of sessions)
31
+ if ('activity' in session && session.activity === 'MANAGED_ACTIVE')
32
+ runningCounts[sessionGroup(session)] += 1;
33
+ const query = typeof input.query === 'string'
34
+ ? input.query.trim().normalize('NFKC').toLocaleLowerCase()
35
+ : '';
36
+ const page = sessionPage(sessions.filter((session) => (input.parentSessionId === undefined ||
37
+ session.parentSessionId === input.parentSessionId) &&
38
+ (input.sessionGroup === undefined || sessionGroup(session) === input.sessionGroup) &&
39
+ (query.length === 0 ||
40
+ (session.customTitle ?? session.runnerTitle ?? '')
41
+ .normalize('NFKC')
42
+ .toLocaleLowerCase()
43
+ .includes(query))), input);
25
44
  return input.view === 'marctl'
26
45
  ? {
27
46
  view: 'marctl',
28
47
  sessions: page.sessions.map((session) => marctlSessionSummary(this.options.runtime, session)),
29
48
  nextCursor: page.nextCursor
30
49
  }
31
- : page;
50
+ : { ...page, runningCounts };
32
51
  }
33
52
  async graph(input) {
34
53
  if (typeof input.workspaceId !== 'string')
@@ -125,14 +144,7 @@ export class SessionCatalogService {
125
144
  ].map((session) => [session.id, session]));
126
145
  if (workspaceId !== undefined)
127
146
  this.mergePersisted(merged, workspaceId, !registeredOnly && query === undefined && this.options.discoveryPending(workspaceId));
128
- const sessions = [...merged.values()].filter((session) => {
129
- if (query === undefined)
130
- return true;
131
- const title = (session.customTitle ?? session.runnerTitle ?? '')
132
- .normalize('NFKC')
133
- .toLocaleLowerCase();
134
- return title.includes(query);
135
- });
147
+ const sessions = [...merged.values()];
136
148
  return workspaceId === undefined
137
149
  ? sessions
138
150
  : sessions.filter((session) => session.workspaceId === workspaceId);
@@ -24,6 +24,13 @@ export class SessionLifecycleService {
24
24
  this.starts.clear();
25
25
  }
26
26
  async updateMetadata(input) {
27
+ if (input.parentSessionId !== undefined ||
28
+ (input.clearParent !== undefined &&
29
+ (input.clearParent !== true ||
30
+ input.customTitle !== undefined ||
31
+ input.pinned !== undefined ||
32
+ input.sessionPrompt !== undefined)))
33
+ throw new Error('SESSION_INVALID');
27
34
  const sessionPrompt = input.sessionPrompt === undefined ? undefined : normalizeSessionPrompt(input.sessionPrompt);
28
35
  if (typeof input.sessionId !== 'string' ||
29
36
  (input.customTitle !== undefined &&
@@ -39,6 +46,27 @@ export class SessionLifecycleService {
39
46
  (await this.options.projection.createMetadataProjection(input.sessionId));
40
47
  if (current === undefined)
41
48
  throw new Error('SESSION_NOT_FOUND');
49
+ if (input.clearParent === true) {
50
+ if (current.externalSessionId === null)
51
+ throw new Error('SESSION_ACTIVATION_REQUIRED');
52
+ const metadata = this.options
53
+ .database()
54
+ .getNativeSessionMetadata(current.runner, current.externalSessionId, current.workspaceId);
55
+ const parentSessionId = metadata === undefined ? current.parentSessionId : metadata.parentSessionId;
56
+ if (parentSessionId == null) {
57
+ const next = current.parentSessionId == null
58
+ ? current
59
+ : this.options.runtime.clearSessionParent(current.id);
60
+ return { session: this.options.present(next) };
61
+ }
62
+ this.options
63
+ .database()
64
+ .clearSessionParent(current.runner, current.externalSessionId, current.workspaceId);
65
+ const next = this.options.runtime.clearSessionParent(current.id);
66
+ this.options.parentCleared?.(next);
67
+ this.options.emitSession(next);
68
+ return { session: this.options.present(next) };
69
+ }
42
70
  if (sessionPrompt !== undefined && current.externalSessionId === null)
43
71
  throw new Error('SESSION_ACTIVATION_REQUIRED');
44
72
  if (input.pinned === false) {
@@ -12,7 +12,7 @@ export class SessionPresentationService {
12
12
  session = {
13
13
  ...session,
14
14
  sessionPrompt: metadata.sessionPrompt ?? null,
15
- parentSessionId: metadata.parentSessionId ?? session.parentSessionId ?? null,
15
+ parentSessionId: metadata.parentSessionId ?? null,
16
16
  pinnedAt: metadata.pinnedAt,
17
17
  pinOrder: metadata.pinOrder
18
18
  };
@@ -26,6 +26,7 @@ export class SessionPresentationService {
26
26
  return {
27
27
  ...session,
28
28
  parentSessionId: session.parentSessionId ?? null,
29
+ sessionGroup: sessionGroup(session),
29
30
  ...presentation,
30
31
  runtime: {
31
32
  ...presentation.runtime,
@@ -60,6 +61,9 @@ export class SessionPresentationService {
60
61
  });
61
62
  }
62
63
  }
64
+ export function sessionGroup(session) {
65
+ return session.parentSessionId ? 'CHILD' : 'MAIN';
66
+ }
63
67
  export function latestSessionActivityAt(current, discovered) {
64
68
  const known = [current, discovered].filter((value) => Number.isFinite(value) && value > 0);
65
69
  return known.length === 0 ? 0 : Math.max(...known);
@@ -48,6 +48,7 @@ export class WorkbenchManifestService {
48
48
  },
49
49
  directTransfer: capabilities.workspaceDirectTransfer,
50
50
  terminalDirect: capabilities.terminalDirect,
51
+ sessionGroups: capabilities.sessionGroups,
51
52
  git: {
52
53
  available: true,
53
54
  reasonCode: null,
@@ -17,6 +17,9 @@ export class WorkspaceWatchService {
17
17
  if (!plain(input.topics))
18
18
  throw new Error('WORKSPACE_WATCH_INVALID');
19
19
  const sessions = input.topics.sessions === true;
20
+ const sessionGroup = input.topics.sessionGroup;
21
+ if (sessionGroup !== undefined && sessionGroup !== 'MAIN' && sessionGroup !== 'CHILD')
22
+ throw new Error('WORKSPACE_WATCH_INVALID');
20
23
  const changes = input.topics.changes === true;
21
24
  const rawFiles = input.topics.files;
22
25
  let files;
@@ -38,7 +41,7 @@ export class WorkspaceWatchService {
38
41
  }
39
42
  if (!sessions && !changes && files === undefined)
40
43
  throw new Error('WORKSPACE_WATCH_INVALID');
41
- return { sessions, changes, files };
44
+ return { sessions, changes, files, ...(sessionGroup === undefined ? {} : { sessionGroup }) };
42
45
  }
43
46
  key(input) {
44
47
  return `${this.sessionHash(input)}:${this.connectionId(input)}`;
@@ -123,7 +123,7 @@ export class WorkspaceWatchSnapshotService {
123
123
  const subscribers = this.subscribers(workspaceId, topic);
124
124
  if (subscribers.length === 0)
125
125
  return;
126
- if (topic !== 'files') {
126
+ if (topic === 'changes') {
127
127
  const first = subscribers[0];
128
128
  if (first === undefined)
129
129
  return;
@@ -154,7 +154,9 @@ export class WorkspaceWatchSnapshotService {
154
154
  }
155
155
  const views = new Map();
156
156
  for (const watch of subscribers) {
157
- const key = JSON.stringify(watch.topics.files);
157
+ const key = topic === 'files'
158
+ ? JSON.stringify(watch.topics.files)
159
+ : (watch.topics.sessionGroup ?? 'ALL');
158
160
  views.set(key, [...(views.get(key) ?? []), watch]);
159
161
  }
160
162
  await Promise.all([...views.values()].map(async (viewSubscribers) => {
@@ -180,12 +182,12 @@ export class WorkspaceWatchSnapshotService {
180
182
  read(workspace, topic, watch) {
181
183
  const key = topic === 'files'
182
184
  ? `${workspace.id}:files:${JSON.stringify(watch.topics.files)}`
183
- : `${workspace.id}:${topic}:${this.readRevision(workspace.id, topic)}`;
185
+ : `${workspace.id}:${topic}:${watch.topics.sessionGroup ?? 'ALL'}:${this.readRevision(workspace.id, topic)}`;
184
186
  const active = this.reads.get(key);
185
187
  if (active !== undefined)
186
188
  return active;
187
189
  const read = (topic === 'sessions'
188
- ? this.options.listSessions(workspace.id)
190
+ ? this.options.listSessions(workspace.id, watch.topics.sessionGroup)
189
191
  : topic === 'changes'
190
192
  ? workspace.kind === 'GIT_WORKSPACE'
191
193
  ? this.options.changes.current(workspace)
@@ -214,7 +216,7 @@ export class WorkspaceWatchSnapshotService {
214
216
  viewKey(workspaceId, topic, watch) {
215
217
  return topic === 'files'
216
218
  ? `${workspaceId}:files:${JSON.stringify(watch.topics.files)}`
217
- : `${workspaceId}:${topic}`;
219
+ : `${workspaceId}:${topic}:${watch.topics.sessionGroup ?? 'ALL'}`;
218
220
  }
219
221
  readActive(workspaceId, topic) {
220
222
  const prefix = `${workspaceId}:${topic}`;
package/dist/terminal.js CHANGED
@@ -631,7 +631,7 @@ function terminalDimension(value, fallback) {
631
631
  function terminalSequence(value) {
632
632
  if (value === undefined)
633
633
  return 0;
634
- if (typeof value !== 'number' || !Number.isInteger(value) || value < 0)
634
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0)
635
635
  throw new Error('TERMINAL_REPLAY_INVALID');
636
636
  return value;
637
637
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.11.17",
3
+ "version": "0.11.19",
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.11.17",
40
+ "@myagentroam/agent": "0.11.19",
41
41
  "@myagentroam/marctl": "0.11.0",
42
42
  "@myagentroam/code-navigation": "0.11.0",
43
- "@myagentroam/protocol": "0.11.17"
43
+ "@myagentroam/protocol": "0.11.19"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/ws": "^8.18.1"