@myagentroam/node 0.9.150 → 0.9.152

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.
@@ -219,7 +219,7 @@ export class MarAgentManagedRunController {
219
219
  if (!execution)
220
220
  return false;
221
221
  execution.cancel();
222
- this.host.emit(runId, 'run.cancelling', { intent }, 'RUNNING');
222
+ this.host.emit(runId, 'run.cancelling', { intent });
223
223
  return true;
224
224
  }
225
225
  respondUserInput(runId, requestId, answers) {
@@ -236,6 +236,20 @@ export class MarAgentManagedRunController {
236
236
  return true;
237
237
  }
238
238
  async run(input) {
239
+ const controller = new AbortController();
240
+ const titleController = new AbortController();
241
+ let cancelExecution;
242
+ // Own cancellation before any asynchronous preparation, not only after SDK admission.
243
+ this.#executions.set(input.runId, {
244
+ cancel: () => {
245
+ if (controller.signal.aborted)
246
+ return;
247
+ controller.abort();
248
+ titleController.abort();
249
+ cancelExecution?.();
250
+ },
251
+ cancelTitle: () => titleController.abort()
252
+ });
239
253
  this.host.active.add(input.runId);
240
254
  this.host.adopt({
241
255
  runId: input.runId,
@@ -253,9 +267,10 @@ export class MarAgentManagedRunController {
253
267
  let executionMcpBinding;
254
268
  let computerUseBinding;
255
269
  let executionModels = [];
256
- const titleController = new AbortController();
257
270
  let titleTask;
271
+ let cancelledDuringPreparation = false;
258
272
  try {
273
+ controller.signal.throwIfAborted();
259
274
  const sessionKey = input.externalSessionId ?? input.sessionId;
260
275
  // Reserve both identities while opening so a concurrent release can await the same runtime.
261
276
  const acquisitionKeys = [...new Set([sessionKey, input.sessionId])];
@@ -273,6 +288,7 @@ export class MarAgentManagedRunController {
273
288
  if (this.#runtimeAcquisitions.get(key) === acquisition)
274
289
  this.#runtimeAcquisitions.delete(key);
275
290
  }
291
+ controller.signal.throwIfAborted();
276
292
  if (this.#disposed ||
277
293
  this.#sessionReleases.has(sessionKey) ||
278
294
  this.#sessionReleases.has(input.sessionId) ||
@@ -284,6 +300,7 @@ export class MarAgentManagedRunController {
284
300
  throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
285
301
  runtime.activeRunId = input.runId;
286
302
  await runtime.mcpLease.pool.refreshForExecution();
303
+ controller.signal.throwIfAborted();
287
304
  const executionMcpServers = input.mcpServers
288
305
  .filter(isExecutionScopedMcp)
289
306
  .map((server) => ({ ...server, lifecycle: 'EXECUTION' }));
@@ -294,6 +311,7 @@ export class MarAgentManagedRunController {
294
311
  enabled: input.computerUseEnabled === true,
295
312
  modelSupportsImages: input.model.inputCapabilities.includes('IMAGE')
296
313
  });
314
+ controller.signal.throwIfAborted();
297
315
  if (this.#disposed ||
298
316
  this.#sessionReleases.has(sessionKey) ||
299
317
  this.#sessionReleases.has(input.sessionId) ||
@@ -309,6 +327,7 @@ export class MarAgentManagedRunController {
309
327
  authorizationScope: runtime.session.id
310
328
  });
311
329
  }
330
+ controller.signal.throwIfAborted();
312
331
  const userTools = executionMcpLease?.pool.definitions() ?? [];
313
332
  const nodeTools = computerUseBinding?.tools ?? [];
314
333
  if (userTools.some((tool) => tool.name.startsWith('mcp__computer__')))
@@ -378,6 +397,7 @@ export class MarAgentManagedRunController {
378
397
  const metadata = input.mode === 'compact' || !runtime.automaticTitleEnabled
379
398
  ? undefined
380
399
  : await runtime.agent.getSession(session.id).catch(() => undefined);
400
+ controller.signal.throwIfAborted();
381
401
  const handle = input.mode === 'plan'
382
402
  ? session.plan({
383
403
  ...runInput
@@ -385,10 +405,7 @@ export class MarAgentManagedRunController {
385
405
  : input.mode === 'compact'
386
406
  ? session.compact(runInput)
387
407
  : session.run(runInput);
388
- this.#executions.set(input.runId, {
389
- cancel: () => handle.cancel(),
390
- cancelTitle: () => titleController.abort()
391
- });
408
+ cancelExecution = () => handle.cancel();
392
409
  this.host.emit(input.runId, 'run.running', {}, 'RUNNING');
393
410
  for await (const event of handle) {
394
411
  if (event.type === 'execution.started' && metadata?.automaticTitlePending && !titleTask) {
@@ -430,20 +447,26 @@ export class MarAgentManagedRunController {
430
447
  }, status);
431
448
  }
432
449
  catch (error) {
433
- if (runtime !== undefined)
434
- this.#prepareRuntimeForTerminal(input.runId, runtime);
435
- const projected = runnerErrorProjection(error, 'MAR_AGENT_RUN_FAILED');
436
- this.host.emitItem(input.runId, {
437
- itemId: boundedConversationItemId('mar-agent-error', input.runId),
438
- kind: 'error',
439
- status: 'FAILED',
440
- payload: {
441
- ...projected,
442
- recoverable: true,
443
- retryAfterMs: null
444
- }
445
- });
446
- this.host.emit(input.runId, 'run.failed', projected, 'FAILED');
450
+ if (controller.signal.aborted && cancelExecution === undefined) {
451
+ // Keep the Run owned until preparation and its resources actually settle.
452
+ cancelledDuringPreparation = true;
453
+ }
454
+ else {
455
+ if (runtime !== undefined)
456
+ this.#prepareRuntimeForTerminal(input.runId, runtime);
457
+ const projected = runnerErrorProjection(error, 'MAR_AGENT_RUN_FAILED');
458
+ this.host.emitItem(input.runId, {
459
+ itemId: boundedConversationItemId('mar-agent-error', input.runId),
460
+ kind: 'error',
461
+ status: 'FAILED',
462
+ payload: {
463
+ ...projected,
464
+ recoverable: true,
465
+ retryAfterMs: null
466
+ }
467
+ });
468
+ this.host.emit(input.runId, 'run.failed', projected, 'FAILED');
469
+ }
447
470
  }
448
471
  finally {
449
472
  titleController.abort();
@@ -452,14 +475,16 @@ export class MarAgentManagedRunController {
452
475
  delete runtime.automaticTitleTask;
453
476
  this.clearInteractions(input.runId);
454
477
  this.#normalizers.delete(input.runId);
455
- this.host.active.delete(input.runId);
456
- this.#executions.delete(input.runId);
457
478
  await closeImageCredentialProviders(executionModels);
458
479
  await computerUseBinding?.close().catch(() => undefined);
459
480
  runtime?.host.unbindExecutionMcp(executionMcpBinding);
460
481
  await executionMcpLease?.release().catch(() => undefined);
461
482
  if (runtime !== undefined)
462
483
  await this.#finishRuntimeExecution(input.runId, runtime).catch(() => undefined);
484
+ this.host.active.delete(input.runId);
485
+ this.#executions.delete(input.runId);
486
+ if (cancelledDuringPreparation)
487
+ this.host.emit(input.runId, 'run.cancelled', { status: 'CANCELLED' }, 'CANCELLED');
463
488
  }
464
489
  }
465
490
  #prepareRuntimeForTerminal(runId, runtime) {
@@ -1,4 +1,4 @@
1
- const TURN_PAGE_SIZE = 30;
1
+ const MAX_INITIAL_TURN_PAGE_SIZE = 30;
2
2
  function conversationSegmentPrefix(turn) {
3
3
  const user = turn.items.find((item) => item.kind === 'user_message');
4
4
  const payload = typeof user?.payload === 'object' && user.payload !== null && !Array.isArray(user.payload)
@@ -89,14 +89,16 @@ export class ConversationSegmentService {
89
89
  const boundary = input.cursor === undefined ? undefined : decodeCursor(input.cursor, session.id);
90
90
  const collected = [];
91
91
  const visited = new Set();
92
- let turnCursor;
92
+ let turnCursor = boundary?.turnCursor ?? undefined;
93
+ let oldestTurnCursor = null;
93
94
  let locatingBoundary = boundary !== undefined;
94
95
  let hasOlder = false;
95
96
  let metadata;
96
97
  while (collected.length < limit) {
97
98
  const page = await this.readTurns(session, {
98
99
  ...(turnCursor === undefined ? {} : { cursor: turnCursor }),
99
- limit: TURN_PAGE_SIZE
100
+ // Preserve the bounded live window without crossing its deferred history boundary.
101
+ limit: boundary === undefined ? Math.min(limit, MAX_INITIAL_TURN_PAGE_SIZE) : 1
100
102
  });
101
103
  metadata ??= {
102
104
  snapshotSequence: page.snapshotSequence,
@@ -123,6 +125,7 @@ export class ConversationSegmentService {
123
125
  const take = eligible.slice(-remaining);
124
126
  hasOlder = eligible.length > take.length || page.nextCursor !== null;
125
127
  collected.unshift(...take);
128
+ oldestTurnCursor = turnCursor ?? null;
126
129
  }
127
130
  if (boundary === undefined && page.deferredOlderHistory === true)
128
131
  break;
@@ -140,7 +143,8 @@ export class ConversationSegmentService {
140
143
  : encodeCursor({
141
144
  version: 1,
142
145
  sessionId: session.id,
143
- beforeSegmentId: collected[0].id
146
+ beforeSegmentId: collected[0].id,
147
+ turnCursor: oldestTurnCursor
144
148
  });
145
149
  return {
146
150
  ...(metadata ?? {
@@ -164,7 +168,10 @@ function decodeCursor(value, sessionId) {
164
168
  if (decoded.version !== 1 ||
165
169
  decoded.sessionId !== sessionId ||
166
170
  typeof decoded.beforeSegmentId !== 'string' ||
167
- decoded.beforeSegmentId.length === 0)
171
+ decoded.beforeSegmentId.length === 0 ||
172
+ (decoded.turnCursor !== undefined &&
173
+ decoded.turnCursor !== null &&
174
+ (typeof decoded.turnCursor !== 'string' || decoded.turnCursor.length === 0)))
168
175
  throw new Error('EVENT_CURSOR_INVALID');
169
176
  return decoded;
170
177
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.150",
3
+ "version": "0.9.152",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -35,9 +35,9 @@
35
35
  "ws": "^8.21.3",
36
36
  "zod": "4.4.3",
37
37
  "@myagentroam/marctl": "0.9.120",
38
- "@myagentroam/agent": "0.9.150",
38
+ "@myagentroam/protocol": "0.9.152",
39
39
  "@myagentroam/code-navigation": "0.9.129",
40
- "@myagentroam/protocol": "0.9.150"
40
+ "@myagentroam/agent": "0.9.152"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/ws": "^8.18.1"