@myagentroam/node 0.11.73 → 0.12.0

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.
@@ -9,7 +9,8 @@ import { MarAgentRuntimeModelFactory, sdkModelConfigurations, closeImageCredenti
9
9
  import { createImageGenerationExtensions } from './image-generation-extensions.js';
10
10
  import { createWebToolsExtensions } from './web-tools-extensions.js';
11
11
  import { combineExecutionExtensions } from './execution-extensions.js';
12
- import { compactRuntimeConfigurations, marAgentCompactExtensionSource, updateMarAgentCompactState } from './compact-extensions.js';
12
+ import { compactRuntimeConfigurations, updateMarAgentCompactState } from './compact-extensions.js';
13
+ import { marAgentRuntimeExtensionSource } from './runtime-extensions.js';
13
14
  import { webSessionScope } from '../../service/web-fetch-contracts.js';
14
15
  /**
15
16
  * Opens one short-lived MAR Agent Host per hosted Run so template mounts cannot leak across Run
@@ -391,7 +392,7 @@ export class MarAgentHostedConversationRuntime {
391
392
  mode: 'node',
392
393
  host,
393
394
  models: sdkModelConfigurations(models.filter((model) => input.models.some((candidate) => candidate.id === model.id))),
394
- extensions: marAgentCompactExtensionSource(compactState),
395
+ extensions: marAgentRuntimeExtensionSource(compactState),
395
396
  defaultModelId: 'defaultModelId' in input ? input.defaultModelId : input.modelId,
396
397
  workspaceMapping: {
397
398
  logicalPath: '/workspace',
@@ -0,0 +1,33 @@
1
+ import { marAgentCompactExtensionSource } from './compact-extensions.js';
2
+ export function marAgentRuntimeExtensionSource(state) {
3
+ const compactSource = marAgentCompactExtensionSource(state);
4
+ return {
5
+ async resolve(request) {
6
+ const snapshot = await compactSource.resolve(request);
7
+ return {
8
+ ...snapshot,
9
+ extensions: [
10
+ ...snapshot.extensions,
11
+ {
12
+ id: 'mar.node.file-links',
13
+ apiVersion: '1.0',
14
+ attachSession(context) {
15
+ context.hooks.intercept('prompt.sections', (input, _call, next) => next({
16
+ ...input,
17
+ sections: [
18
+ ...input.sections,
19
+ {
20
+ id: 'mar.node.file-links',
21
+ text: input.facts?.mode === 'compact'
22
+ ? null
23
+ : 'When mentioning key files changed in this task, prefer Markdown links to their absolute paths for easy preview.'
24
+ }
25
+ ]
26
+ }));
27
+ }
28
+ }
29
+ ]
30
+ };
31
+ }
32
+ };
33
+ }
@@ -9,7 +9,8 @@ import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
9
9
  import { nodeLog } from '../../operational.js';
10
10
  import { BUNDLED_RIPGREP_VERSION, bundledRipgrepPath, prependBundledRipgrepToPath } from '../../ripgrep-binary.js';
11
11
  import { canonicalJson } from '../../util/canonical-json.js';
12
- import { compactRuntimeConfigurations, marAgentCompactExtensionSource, updateMarAgentCompactState } from './compact-extensions.js';
12
+ import { compactRuntimeConfigurations, updateMarAgentCompactState } from './compact-extensions.js';
13
+ import { marAgentRuntimeExtensionSource } from './runtime-extensions.js';
13
14
  import { MarAgentConversationNormalizer } from './conversation-normalizer.js';
14
15
  import { sdkModelConfigurations } from './runtime-model-factory.js';
15
16
  export const HOST_CONFIGURATION_CATEGORIES = [
@@ -72,7 +73,7 @@ export async function createSessionRuntime(input, sessionKey, fingerprint, hostC
72
73
  });
73
74
  const compactState = { revision: 0 };
74
75
  updateMarAgentCompactState(compactState, input.compact, models, input.models.map((model) => model.id));
75
- const compactSource = marAgentCompactExtensionSource(compactState);
76
+ const runtimeSource = marAgentRuntimeExtensionSource(compactState);
76
77
  const networkProvider = networkContext;
77
78
  agent = await dependencies.createAgent({
78
79
  mode: 'node',
@@ -80,10 +81,10 @@ export async function createSessionRuntime(input, sessionKey, fingerprint, hostC
80
81
  models: sdkModelConfigurations(models.filter((model) => input.models.some((candidate) => candidate.id === model.id))),
81
82
  defaultModelId: input.model.id,
82
83
  extensions: networkProvider === undefined
83
- ? compactSource
84
+ ? runtimeSource
84
85
  : {
85
86
  async resolve(request) {
86
- const snapshot = await compactSource.resolve(request);
87
+ const snapshot = await runtimeSource.resolve(request);
87
88
  return {
88
89
  ...snapshot,
89
90
  extensions: [
@@ -310,45 +310,32 @@ export class MarAgentRunner extends AbstractRunner {
310
310
  try {
311
311
  catalog = await openAgentSessionCatalog({ homeDirectory: this.homeDirectory });
312
312
  const activeCatalog = catalog;
313
- const listed = await activeCatalog.list({ limit: 200 });
314
- if (context.cleanup) {
315
- const sessions = [...listed.sessions];
316
- let cursor = listed.nextCursor;
317
- while (cursor !== undefined) {
318
- const next = await activeCatalog.list({ limit: 200, cursor });
319
- sessions.push(...next.sessions);
320
- cursor = next.nextCursor;
321
- }
322
- const workspaces = workspace ? [workspace] : context.workspaces;
323
- return sessions.flatMap((session) => {
324
- const target = workspaces.find((candidate) => resolve(candidate.path) === resolve(session.workspace));
325
- return target
326
- ? [
327
- context.createProjection({
328
- workspace: target,
329
- runner: this.name,
330
- externalSessionId: session.id,
331
- cwd: session.workspace,
332
- ...(session.source?.parentSessionId === undefined
333
- ? {}
334
- : { parentSessionId: session.source.parentSessionId }),
335
- lastActivityAt: Date.parse(session.lastActivityAt)
336
- })
337
- ]
338
- : [];
339
- });
340
- }
341
313
  const workspaces = workspace ? [workspace] : context.workspaces;
342
- return (await Promise.all(listed.sessions.map(async (session) => {
314
+ const sessions = (await activeCatalog.listSnapshot()).flatMap((session) => {
343
315
  const target = workspaces.find((candidate) => resolve(candidate.path) === resolve(session.workspace));
344
- if (!target)
345
- return [];
346
- const executionConfiguration = session.modelId === undefined
347
- ? await activeCatalog.executionConfiguration(session.id)
348
- : session;
349
- const title = await marAgentSessionTitle(session, activeCatalog);
350
- return [
351
- {
316
+ return target ? [{ session, target }] : [];
317
+ });
318
+ if (context.cleanup) {
319
+ return sessions.map(({ session, target }) => context.createProjection({
320
+ workspace: target,
321
+ runner: this.name,
322
+ externalSessionId: session.id,
323
+ cwd: session.workspace,
324
+ ...(session.source?.parentSessionId === undefined
325
+ ? {}
326
+ : { parentSessionId: session.source.parentSessionId }),
327
+ lastActivityAt: Date.parse(session.lastActivityAt)
328
+ }));
329
+ }
330
+ const discovered = [];
331
+ const batchSize = 8;
332
+ for (let offset = 0; offset < sessions.length; offset += batchSize) {
333
+ const batch = await Promise.allSettled(sessions.slice(offset, offset + batchSize).map(async ({ session, target }) => {
334
+ const executionConfiguration = session.modelId === undefined
335
+ ? await activeCatalog.executionConfiguration(session.id)
336
+ : session;
337
+ const title = await marAgentSessionTitle(session, activeCatalog);
338
+ return {
352
339
  ...context.createProjection({
353
340
  workspace: target,
354
341
  runner: this.name,
@@ -366,14 +353,15 @@ export class MarAgentRunner extends AbstractRunner {
366
353
  ...(executionConfiguration?.reasoningEffort === undefined
367
354
  ? {}
368
355
  : { effort: executionConfiguration.reasoningEffort })
369
- }
370
- ];
371
- }))).flat();
372
- }
373
- catch (error) {
374
- if (context.cleanup)
375
- throw error;
376
- return [];
356
+ };
357
+ }));
358
+ for (const entry of batch) {
359
+ if (entry.status === 'rejected')
360
+ throw entry.reason;
361
+ discovered.push(entry.value);
362
+ }
363
+ }
364
+ return discovered;
377
365
  }
378
366
  finally {
379
367
  await catalog?.close().catch(() => undefined);
@@ -361,8 +361,7 @@ export class NativeSessionProjectionService {
361
361
  projectionForNative(native) {
362
362
  return this.resolveNativeProjection(native, this.options.runtime, false);
363
363
  }
364
- projectionsForNative(native) {
365
- const lookup = this.options.runtime.createSessionLookup();
364
+ projectionsForNative(native, lookup = this.options.runtime.createSessionLookup()) {
366
365
  return new Map(native.map((session) => [session, this.resolveNativeProjection(session, lookup, false)]));
367
366
  }
368
367
  managedForNative(native) {
@@ -76,7 +76,7 @@ export class SessionCatalogService {
76
76
  ancestors.add(parent.id);
77
77
  root = parent;
78
78
  }
79
- const project = this.navigationProjector(input.workspaceId);
79
+ const { project } = this.navigationProjector(input.workspaceId, (id) => sessionActivityStatus(this.options.runtime, id));
80
80
  const queue = [root];
81
81
  const visited = new Set([root.id]);
82
82
  const nodes = [];
@@ -97,10 +97,26 @@ export class SessionCatalogService {
97
97
  }
98
98
  navigation(query) {
99
99
  const database = this.options.database();
100
- const project = this.navigationProjector(query.workspaceId);
100
+ const statuses = new Map();
101
+ const status = (id) => {
102
+ const cached = statuses.get(id);
103
+ if (cached !== undefined)
104
+ return cached;
105
+ const value = sessionActivityStatus(this.options.runtime, id);
106
+ statuses.set(id, value);
107
+ return value;
108
+ };
109
+ let projection;
110
+ const projector = () => (projection ??= this.navigationProjector(query.workspaceId, status));
111
+ const project = (record) => projector().project(record);
112
+ const registered = new Map();
101
113
  const get = (id) => {
114
+ if (registered.has(id))
115
+ return registered.get(id);
102
116
  const record = database.getSessionRecord(id);
103
- return record?.workspaceId === query.workspaceId ? record : undefined;
117
+ const scoped = record?.workspaceId === query.workspaceId ? record : undefined;
118
+ registered.set(id, scoped);
119
+ return scoped;
104
120
  };
105
121
  const requestedIds = new Set([
106
122
  ...query.sessionIds,
@@ -113,19 +129,13 @@ export class SessionCatalogService {
113
129
  return record === undefined ? [] : [project(record)];
114
130
  });
115
131
  const descendantsByAnchor = new Map();
116
- const statuses = new Map();
117
- const status = (id) => {
118
- const cached = statuses.get(id);
119
- if (cached !== undefined)
120
- return cached;
121
- const value = sessionActivityStatus(this.options.runtime, id);
122
- statuses.set(id, value);
123
- return value;
124
- };
125
132
  const descendants = (anchor) => {
126
133
  let records = descendantsByAnchor.get(anchor);
127
134
  if (records === undefined) {
128
- records = database.sessionTreeRecords(query.workspaceId, anchor).slice(1);
135
+ const tree = database.sessionTreeRecords(query.workspaceId, anchor);
136
+ for (const record of tree)
137
+ registered.set(record.id, record);
138
+ records = tree.slice(1);
129
139
  descendantsByAnchor.set(anchor, records);
130
140
  }
131
141
  return records;
@@ -166,12 +176,32 @@ export class SessionCatalogService {
166
176
  activePaths.add(record.metadata.parentSessionId);
167
177
  }
168
178
  }
169
- // Select active paths before idle filler, not after truncating arbitrary records.
170
- // Stable breadth-first order keeps selected children connected to their ancestors.
171
- const prioritized = [
172
- ...records.filter((record) => activePaths.has(record.id)),
173
- ...records.filter((record) => !activePaths.has(record.id))
174
- ];
179
+ const activity = new Map(records.map((record) => [record.id, projector().lastActivityAt(record)]));
180
+ const compare = (left, right) => Number(activePaths.has(right.id)) - Number(activePaths.has(left.id)) ||
181
+ activity.get(right.id) - activity.get(left.id) ||
182
+ (left.id < right.id ? -1 : left.id > right.id ? 1 : 0);
183
+ const prioritized = [...records].sort(compare);
184
+ const byId = new Map(records.map((record) => [record.id, record]));
185
+ const selected = new Map();
186
+ const fill = (candidates) => {
187
+ for (const record of candidates) {
188
+ if (selected.size >= query.limit)
189
+ break;
190
+ const path = [];
191
+ let current = record;
192
+ while (current !== undefined && !selected.has(current.id)) {
193
+ path.push(current);
194
+ current = byId.get(current.metadata.parentSessionId ?? '');
195
+ }
196
+ if (path.length > query.limit - selected.size)
197
+ continue;
198
+ for (const ancestor of path.reverse())
199
+ selected.set(ancestor.id, ancestor);
200
+ }
201
+ };
202
+ // An unfit path must not consume slots before another active session that can fit.
203
+ fill(prioritized.filter((record) => status(record.id) !== 'IDLE'));
204
+ fill(prioritized);
175
205
  const parent = anchor?.metadata.parentSessionId
176
206
  ? get(anchor.metadata.parentSessionId)
177
207
  : undefined;
@@ -180,23 +210,28 @@ export class SessionCatalogService {
180
210
  : {
181
211
  anchorSessionId: anchor.id,
182
212
  parent: parent === undefined ? null : project(parent),
183
- nodes: prioritized.slice(0, query.limit).map(project),
213
+ nodes: [...selected.values()].sort(compare).map(project),
184
214
  truncated: records.length > query.limit
185
215
  };
186
216
  return { sessions, summaries, tree, missingSessionIds };
187
217
  }
188
- navigationProjector(workspaceId) {
218
+ navigationProjector(workspaceId, status) {
219
+ const lookup = this.options.runtime.createSessionLookup();
189
220
  const native = this.options
190
221
  .cachedNative()
191
222
  .filter((session) => session.workspaceId === workspaceId);
192
- const projections = this.options.projection.projectionsForNative(native);
223
+ const projections = this.options.projection.projectionsForNative(native, lookup);
193
224
  const activityById = new Map();
194
225
  for (const source of native) {
195
226
  const id = projections.get(source)?.id ?? source.id;
196
227
  activityById.set(id, latestSessionActivityAt(activityById.get(id) ?? 0, source.lastActivityAt));
197
228
  }
198
- return (session) => {
199
- const active = this.options.runtime.getAgentSession(session.id);
229
+ const current = (session) => lookup.getAgentSessionById(session.id) ??
230
+ lookup.findAgentSessionByExternalSession(session.runner, session.id, workspaceId);
231
+ const lastActivityAt = (session) => latestSessionActivityAt(current(session)?.lastActivityAt ?? 0, activityById.get(session.id) ?? 0);
232
+ const projected = new Map();
233
+ const project = (session) => {
234
+ const active = current(session);
200
235
  const live = active === undefined ? undefined : this.options.present(active);
201
236
  return {
202
237
  id: session.id,
@@ -208,10 +243,21 @@ export class SessionCatalogService {
208
243
  'Untitled session',
209
244
  runner: session.runner,
210
245
  activity: live && 'activity' in live ? live.activity : 'IDLE',
211
- status: sessionActivityStatus(this.options.runtime, session.id),
246
+ status: status(session.id),
212
247
  lastActivityAt: latestSessionActivityAt(live?.lastActivityAt ?? 0, activityById.get(session.id) ?? 0)
213
248
  };
214
249
  };
250
+ return {
251
+ lastActivityAt,
252
+ project: (session) => {
253
+ let value = projected.get(session.id);
254
+ if (value === undefined) {
255
+ value = project(session);
256
+ projected.set(session.id, value);
257
+ }
258
+ return value;
259
+ }
260
+ };
215
261
  }
216
262
  async collect(input) {
217
263
  const workspaceId = typeof input.workspaceId === 'string' ? input.workspaceId : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.11.73",
3
+ "version": "0.12.0",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -40,10 +40,10 @@
40
40
  "turndown-plugin-gfm": "^1.0.2",
41
41
  "ws": "^8.21.3",
42
42
  "zod": "4.4.3",
43
- "@myagentroam/agent": "0.11.73",
43
+ "@myagentroam/agent": "0.12.0",
44
+ "@myagentroam/marctl": "0.12.0",
44
45
  "@myagentroam/code-navigation": "0.11.1",
45
- "@myagentroam/protocol": "0.11.73",
46
- "@myagentroam/marctl": "0.11.12"
46
+ "@myagentroam/protocol": "0.12.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/turndown": "^5.0.6",