@myagentroam/node 0.12.6 → 0.12.8

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.
@@ -10,7 +10,8 @@ export function composeSessionProjection(input) {
10
10
  runners: input.runners,
11
11
  capabilities: input.capabilities,
12
12
  hasManagedActiveRun: (session) => hasManagedActiveRun(input.runtime, session),
13
- projectionForNative: (session) => native.projectionForNative(session)
13
+ projectionForNative: (session) => native.projectionForNative(session),
14
+ cachedNativeActivityAt: (session) => native.cachedActivityAt(session)
14
15
  });
15
16
  const native = new NativeSessionProjectionService({
16
17
  database: input.database,
package/dist/connector.js CHANGED
@@ -546,21 +546,14 @@ export class NodeConnector {
546
546
  const run = this.runtime.activeSessionRun(currentSessionId);
547
547
  if (!run || !run.initiatedByUserId)
548
548
  return [];
549
- const result = await this.sessionCatalogService.operations()['session.list']({
550
- workspaceId: currentSession.workspaceId,
551
- registeredOnly: true,
552
- view: 'marctl',
553
- parentSessionId: currentSessionId,
554
- limit: 100
555
- });
549
+ const data = await this.sessionCatalogService.listRegisteredChildren(currentSession.workspaceId, currentSessionId);
556
550
  signal.throwIfAborted();
557
- const data = result;
558
551
  const truncated = data.nextCursor != null;
559
- if (!data.sessions?.length && !truncated)
552
+ if (data.sessions.length === 0 && !truncated)
560
553
  return [];
561
554
  return [
562
555
  `Child Sessions (use agent_list for details): ${JSON.stringify({
563
- sessions: data.sessions ?? [],
556
+ sessions: data.sessions,
564
557
  truncated
565
558
  }).replace(/[<>&]/g, (c) => c === '<' ? '\\u003c' : c === '>' ? '\\u003e' : '\\u0026')}`
566
559
  ];
@@ -1 +1 @@
1
- {"type":"module","version":"0.12.6"}
1
+ {"type":"module","version":"0.12.8"}
@@ -217,17 +217,31 @@ export class ClaudeCodeRunner extends AbstractRunner {
217
217
  return this.execution.managedSessionByNativeId.get(nativeSessionId);
218
218
  }
219
219
  nativeAliasesFor(managedSessionId) {
220
- return [...this.execution.managedSessionByNativeId]
221
- .filter(([, sessionId]) => sessionId === managedSessionId)
222
- .map(([nativeSessionId]) => nativeSessionId);
220
+ return [...(this.execution.nativeIdsByManagedSession.get(managedSessionId) ?? [])];
223
221
  }
224
222
  forgetNativeAlias(nativeSessionId) {
223
+ const managedSessionId = this.execution.managedSessionByNativeId.get(nativeSessionId);
224
+ if (managedSessionId !== undefined) {
225
+ const aliases = this.execution.nativeIdsByManagedSession.get(managedSessionId);
226
+ aliases?.delete(nativeSessionId);
227
+ if (aliases?.size === 0)
228
+ this.execution.nativeIdsByManagedSession.delete(managedSessionId);
229
+ }
225
230
  this.execution.managedSessionByNativeId.delete(nativeSessionId);
226
231
  this.execution.managedNativeIds.delete(nativeSessionId);
227
232
  }
228
233
  rememberManagedNative(session, nativeSessionId) {
234
+ const previousId = this.execution.managedSessionByNativeId.get(nativeSessionId);
235
+ if (previousId !== undefined && previousId !== session.id)
236
+ this.forgetNativeAlias(nativeSessionId);
229
237
  this.execution.managedNativeIds.add(nativeSessionId);
230
238
  this.execution.managedSessionByNativeId.set(nativeSessionId, session.id);
239
+ let aliases = this.execution.nativeIdsByManagedSession.get(session.id);
240
+ if (aliases === undefined) {
241
+ aliases = new Set();
242
+ this.execution.nativeIdsByManagedSession.set(session.id, aliases);
243
+ }
244
+ aliases.add(nativeSessionId);
231
245
  }
232
246
  async readManagedRunnerTitle(session, nativeSessionId) {
233
247
  let history = await readNativeSession(this.name, session.cwd, nativeSessionId);
@@ -299,6 +313,7 @@ export class ClaudeExecutionState {
299
313
  runs = new Map();
300
314
  managedNativeIds = new Set();
301
315
  managedSessionByNativeId = new Map();
316
+ nativeIdsByManagedSession = new Map();
302
317
  approvals = new Map();
303
318
  userInputs = new Map();
304
319
  }
@@ -132,7 +132,8 @@ function identity(session) {
132
132
  runner: session.runner
133
133
  };
134
134
  }
135
- export function marctlSessionSummary(runtime, session) {
135
+ /** Neutral catalog summary shared by CLI presentation and internal Agent context. */
136
+ export function sessionActivitySummary(runtime, session) {
136
137
  const { status, runId, latestActivity } = activity(runtime, session.id);
137
138
  if (latestActivity === null)
138
139
  return { ...identity(session), status, runId, latestActivity: null };
@@ -1,19 +1,40 @@
1
1
  import { deduplicateDirectSessions } from './runner-native-session-parsers.js';
2
+ import { latestSessionActivityAt } from './session-presentation-service.js';
2
3
  /** Owns native identity/metadata projection, not Runner execution.
3
4
  * Discovery borrows its caller's lifetime; cancellation revokes database projection before
4
5
  * releasing the wait on a third-party read that may not itself support interruption.
6
+ * Ordered revisions keep older discovery snapshots from replacing newer reads or history updates.
5
7
  */
6
8
  export class NativeSessionProjectionService {
7
9
  options;
10
+ revision = 0;
8
11
  directNative = new Map();
9
12
  cachedNative() {
10
- return [...this.directNative.values()];
13
+ return [...this.directNative.values()].map((entry) => entry.session);
14
+ }
15
+ cachedSessionsFor(sessions) {
16
+ return sessions.flatMap((session) => {
17
+ const current = this.directNative.get(session.id)?.session;
18
+ return current?.runner === session.runner && current.workspaceId === session.workspaceId
19
+ ? [current]
20
+ : [];
21
+ });
22
+ }
23
+ cachedActivityAt(session) {
24
+ let at = 0;
25
+ for (const id of this.nativeIds(session)) {
26
+ const native = this.directNative.get(id)?.session;
27
+ if (native?.runner === session.runner && native.workspaceId === session.workspaceId)
28
+ at = latestSessionActivityAt(at, native.lastActivityAt);
29
+ }
30
+ return at;
11
31
  }
12
32
  constructor(options) {
13
33
  this.options = options;
14
34
  }
15
35
  async discover(runnerName, workspaceId, cleanup = false, signal) {
16
36
  signal?.throwIfAborted();
37
+ const revision = ++this.revision;
17
38
  const database = this.requireDatabase();
18
39
  const workspace = workspaceId === undefined ? undefined : database.getWorkspace(workspaceId);
19
40
  if (workspaceId !== undefined && workspace === undefined)
@@ -33,11 +54,8 @@ export class NativeSessionProjectionService {
33
54
  signal?.throwIfAborted();
34
55
  const ordered = deduplicateDirectSessions(sessions);
35
56
  if (!cleanup) {
36
- this.remember(ordered);
37
- return ordered.flatMap((session) => {
38
- const accepted = this.directNative.get(session.id);
39
- return accepted === undefined ? [] : [accepted];
40
- });
57
+ this.rememberAtRevision(ordered, revision);
58
+ return this.cachedSessionsFor(ordered);
41
59
  }
42
60
  const candidates = new Map(ordered.map((session) => [session.id, session]));
43
61
  return ordered.flatMap((session) => {
@@ -59,16 +77,18 @@ export class NativeSessionProjectionService {
59
77
  return { sessions: sessions.map(this.options.present) };
60
78
  }
61
79
  async list() {
80
+ const revision = ++this.revision;
62
81
  const discovered = await Promise.all(this.options.runners
63
82
  .advertised()
64
83
  .filter((runner) => runner.available(this.options.capabilities()))
65
84
  .map((runner) => this.discover(runner.name)));
66
85
  const sessions = deduplicateDirectSessions(discovered.flat());
67
- this.remember(sessions);
68
- return sessions.map(this.options.present);
86
+ this.rememberAtRevision(sessions, revision);
87
+ return this.cachedSessionsFor(sessions).map(this.options.present);
69
88
  }
70
89
  async discoverWorkspace(workspaceId, onRunnerDiscovered, cleanup = false, signal) {
71
90
  signal?.throwIfAborted();
91
+ const revision = ++this.revision;
72
92
  const discovered = await Promise.allSettled(this.options.runners
73
93
  .advertised()
74
94
  .filter((runner) => runner.available(this.options.capabilities()))
@@ -85,11 +105,11 @@ export class NativeSessionProjectionService {
85
105
  throw failed.reason;
86
106
  const sessions = deduplicateDirectSessions(discovered.flatMap((result) => (result.status === 'fulfilled' ? result.value : [])));
87
107
  if (!cleanup)
88
- this.remember(sessions);
89
- return sessions;
108
+ this.rememberAtRevision(sessions, revision);
109
+ return cleanup ? sessions : this.cachedSessionsFor(sessions);
90
110
  }
91
111
  async resolve(sessionId, registeredOnly = false) {
92
- const cached = this.directNative.get(sessionId);
112
+ const cached = this.directNative.get(sessionId)?.session;
93
113
  if (cached !== undefined)
94
114
  return cached;
95
115
  const database = this.options.database();
@@ -279,6 +299,9 @@ export class NativeSessionProjectionService {
279
299
  return true;
280
300
  }
281
301
  remember(sessions) {
302
+ this.rememberAtRevision(sessions, ++this.revision);
303
+ }
304
+ rememberAtRevision(sessions, revision) {
282
305
  const database = this.options.database();
283
306
  const candidates = new Map(sessions.map((session) => [session.id, session]));
284
307
  const valid = new Map(sessions.map((session) => [session.id, this.validParent(session, candidates)]));
@@ -312,7 +335,9 @@ export class NativeSessionProjectionService {
312
335
  for (const session of accepted)
313
336
  visit(session);
314
337
  for (const session of ordered) {
315
- this.directNative.set(session.id, session);
338
+ if ((this.directNative.get(session.id)?.revision ?? 0) > revision)
339
+ continue;
340
+ this.directNative.set(session.id, { session, revision });
316
341
  if (database === undefined || session.externalSessionId === null)
317
342
  continue;
318
343
  const metadata = database.getNativeSessionMetadata(session.runner, session.externalSessionId, session.workspaceId);
@@ -339,10 +364,7 @@ export class NativeSessionProjectionService {
339
364
  record.id,
340
365
  record.metadata.customTitle ?? record.metadata.runnerTitle ?? 'Untitled session'
341
366
  ]));
342
- for (const session of [
343
- ...this.directNative.values(),
344
- ...this.options.runtime.listAgentSessions()
345
- ]) {
367
+ for (const session of [...this.cachedNative(), ...this.options.runtime.listAgentSessions()]) {
346
368
  if (session.workspaceId !== workspaceId)
347
369
  continue;
348
370
  const metadata = recordById.get(session.id)?.metadata;
@@ -425,7 +447,7 @@ export class NativeSessionProjectionService {
425
447
  }
426
448
  }
427
449
  const directIds = new Set();
428
- for (const [directId, direct] of this.directNative) {
450
+ for (const [directId, { session: direct }] of this.directNative) {
429
451
  if (direct.runner === session.runner &&
430
452
  direct.cwd === session.cwd &&
431
453
  direct.externalSessionId !== null &&
@@ -5,10 +5,11 @@ import { nodeLog } from '../operational.js';
5
5
  import { isTerminalRunStatus } from './runtime/type-guards.js';
6
6
  import { sessionPage } from './pagination.js';
7
7
  import { latestSessionActivityAt } from './session-presentation-service.js';
8
- import { marctlSessionSummary, sessionActivityStatus } from '../service/session-recent-activity.js';
8
+ import { sessionActivitySummary, sessionActivityStatus } from '../service/session-recent-activity.js';
9
9
  import { WorkspaceSessionCleanupBudget, WorkspaceSessionCleanupDeadline } from '../service/workspace-session-cleanup-budget.js';
10
10
  /** Owns catalog queries and Session catalog mutations; borrows the Workspace native cache.
11
- * Public operations and internal observation share query rules, not transport/admission routing.
11
+ * Public operations adapt transport views; internal readers use typed queries and neutral
12
+ * activity summaries. Both share query rules, never transport/admission routing.
12
13
  */
13
14
  export class SessionCatalogService {
14
15
  options;
@@ -18,12 +19,15 @@ export class SessionCatalogService {
18
19
  }
19
20
  /** Internal observation uses the same catalog query without public operation dispatch. */
20
21
  listWorkspacePage(workspaceId, sessionGroup) {
21
- return this.list({
22
+ return this.queryPage({
22
23
  workspaceId,
23
24
  limit: 30,
24
25
  ...(sessionGroup === undefined ? {} : { sessionGroup })
25
26
  });
26
27
  }
28
+ async listRegisteredChildren(workspaceId, parentSessionId) {
29
+ return this.summarizePage(await this.queryPage({ workspaceId, parentSessionId, registeredOnly: true, limit: 100 }));
30
+ }
27
31
  operations() {
28
32
  return {
29
33
  'session.list': (data) => this.list(record(data)),
@@ -35,6 +39,16 @@ export class SessionCatalogService {
35
39
  };
36
40
  }
37
41
  async list(input) {
42
+ const page = await this.queryPage(input);
43
+ return input.view === 'marctl' ? { view: 'marctl', ...this.summarizePage(page) } : page;
44
+ }
45
+ summarizePage(page) {
46
+ return {
47
+ sessions: page.sessions.map((session) => sessionActivitySummary(this.options.runtime, session)),
48
+ nextCursor: page.nextCursor
49
+ };
50
+ }
51
+ async queryPage(input) {
38
52
  if (input.sessionGroup !== undefined &&
39
53
  input.sessionGroup !== 'MAIN' &&
40
54
  input.sessionGroup !== 'CHILD')
@@ -55,13 +69,7 @@ export class SessionCatalogService {
55
69
  .normalize('NFKC')
56
70
  .toLocaleLowerCase()
57
71
  .includes(query))), input);
58
- return input.view === 'marctl'
59
- ? {
60
- view: 'marctl',
61
- sessions: page.sessions.map((session) => marctlSessionSummary(this.options.runtime, session)),
62
- nextCursor: page.nextCursor
63
- }
64
- : { ...page, runningCounts };
72
+ return { ...page, runningCounts };
65
73
  }
66
74
  async graph(input) {
67
75
  if (typeof input.workspaceId !== 'string')
@@ -1,4 +1,7 @@
1
1
  import { ScheduledSessionStore } from '../service/scheduled-session-store.js';
2
+ /** Projects current ownership and observed native activity for every Session consumer.
3
+ * Borrows Runtime and the native cache; presentation never discovers or reads history.
4
+ */
2
5
  export class SessionPresentationService {
3
6
  options;
4
7
  constructor(options) {
@@ -13,6 +16,10 @@ export class SessionPresentationService {
13
16
  ...current,
14
17
  lastActivityAt: latestSessionActivityAt(current.lastActivityAt, session.lastActivityAt)
15
18
  };
19
+ session = {
20
+ ...session,
21
+ lastActivityAt: latestSessionActivityAt(session.lastActivityAt, this.options.cachedNativeActivityAt(session))
22
+ };
16
23
  const database = this.options.database();
17
24
  if (session.externalSessionId !== null && database !== undefined) {
18
25
  const metadata = database.getNativeSessionMetadata(session.runner, session.externalSessionId, session.workspaceId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -42,14 +42,14 @@
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.6",
46
- "@myagentroam/protocol": "0.12.6"
45
+ "@myagentroam/agent": "0.12.8",
46
+ "@myagentroam/protocol": "0.12.8"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/turndown": "^5.0.6",
50
50
  "@types/ws": "^8.18.1",
51
- "@myagentroam/marctl": "0.12.6",
52
- "@myagentroam/code-navigation": "0.11.1"
51
+ "@myagentroam/code-navigation": "0.11.1",
52
+ "@myagentroam/marctl": "0.12.8"
53
53
  },
54
54
  "exports": {
55
55
  "./config": {