@myagentroam/node 0.9.135 → 0.9.137

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.
@@ -5,7 +5,7 @@ import { homedir } from 'node:os';
5
5
  import { resolve } from 'node:path';
6
6
  import { discoverAllNativeSessions } from '../native-session-history.js';
7
7
  import { readCodexNativeContextUsage } from '../native-session-history.js';
8
- import { codexThreadCwd, deduplicateDirectSessions, extractCodexThreads, isWithinWorkspace, latestNativeActivity } from './codex/conversation-parser.js';
8
+ import { codexThreadCwd, codexThreadHistoryModes, codexPaginatedThread, codexPaginatedCursor, codexPaginatedConversationPage, deduplicateDirectSessions, extractCodexThreads, isWithinWorkspace, latestNativeActivity } from './codex/conversation-parser.js';
9
9
  import { codexSessionControl } from '../codex-app-server.js';
10
10
  import { extractId } from '../util/runner-native-session-parsers.js';
11
11
  import { nodeLog } from '../operational.js';
@@ -19,6 +19,8 @@ export class CodexRunner extends AbstractRunner {
19
19
  discoveryScope = 'ALL_WORKSPACES';
20
20
  execution = new CodexExecutionState();
21
21
  fastEnabled = false;
22
+ threadHistoryModes = new Map();
23
+ threadHistoryModeReads = new Map();
22
24
  runClients = new Map();
23
25
  notificationListeners = new Set();
24
26
  clientNotificationDisposers = new Map();
@@ -265,9 +267,36 @@ export class CodexRunner extends AbstractRunner {
265
267
  return input.title;
266
268
  }
267
269
  async readConversationHistory(readers) {
268
- // Codex JSONL is the only source for normal history reads.
269
- const native = await readers.native();
270
- return native === undefined ? undefined : { ...native, source: 'native' };
270
+ const page = await readers.official();
271
+ return page === undefined ? undefined : { ...page, source: 'official' };
272
+ }
273
+ async readOfficialConversation(session, input, managedTurn, registerImages) {
274
+ if (session.externalSessionId === null)
275
+ return undefined;
276
+ const threadId = session.externalSessionId;
277
+ const cursor = codexPaginatedCursor(session.id, input.cursor);
278
+ const limit = Math.min(Math.max(input.limit ?? 30, 1), 100);
279
+ await this.start();
280
+ await this.readThreadHistoryMode(threadId);
281
+ const page = await this.client.listThreadTurns(threadId, {
282
+ ...(cursor === undefined ? {} : { cursor }),
283
+ limit
284
+ });
285
+ const nextCursor = typeof page === 'object' && page !== null && 'nextCursor' in page
286
+ ? page.nextCursor
287
+ : undefined;
288
+ let earlierTurnId = null;
289
+ if (typeof nextCursor === 'string') {
290
+ const earlier = await this.client.listThreadTurns(threadId, { cursor: nextCursor, limit: 1 });
291
+ if (typeof earlier !== 'object' ||
292
+ earlier === null ||
293
+ !('data' in earlier) ||
294
+ !Array.isArray(earlier.data) ||
295
+ typeof earlier.data[0]?.id !== 'string')
296
+ throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
297
+ earlierTurnId = earlier.data[0].id;
298
+ }
299
+ return codexPaginatedConversationPage(session, page, earlierTurnId, managedTurn, registerImages);
271
300
  }
272
301
  acceptsTruncatedNativeHistory() {
273
302
  return true;
@@ -340,7 +369,8 @@ export class CodexRunner extends AbstractRunner {
340
369
  let threads = [];
341
370
  try {
342
371
  await this.start();
343
- threads = extractCodexThreads(await this.listThreads());
372
+ const result = await this.listThreads();
373
+ threads = extractCodexThreads(result);
344
374
  }
345
375
  catch {
346
376
  // Native transcripts remain a valid read-only discovery source.
@@ -427,7 +457,8 @@ export class CodexRunner extends AbstractRunner {
427
457
  'session.rename',
428
458
  'session.remove',
429
459
  'session.pin',
430
- ...(session.externalSessionId === null ? [] : ['session.rewind'])
460
+ ...(session.externalSessionId === null ? [] : ['session.rewind']),
461
+ ...(this.supportsRollback(session) ? ['session.rollback'] : [])
431
462
  ],
432
463
  reasonCode: null
433
464
  }
@@ -498,6 +529,8 @@ export class CodexRunner extends AbstractRunner {
498
529
  client.clearEnvironment();
499
530
  }
500
531
  this.runClients.clear();
532
+ this.threadHistoryModes.clear();
533
+ this.threadHistoryModeReads.clear();
501
534
  this.client.stop();
502
535
  if (this.profileCacheTimer !== undefined)
503
536
  clearTimeout(this.profileCacheTimer);
@@ -539,11 +572,52 @@ export class CodexRunner extends AbstractRunner {
539
572
  respond(requestId, result, runId) {
540
573
  this.clientFor(runId).respond(requestId, result);
541
574
  }
542
- listThreads() {
543
- return this.client.listThreads();
575
+ async listThreads() {
576
+ const result = await this.client.listThreads();
577
+ this.observeThreadHistoryModes(result);
578
+ return result;
579
+ }
580
+ observeThreadHistoryModes(result, expectedThreadId) {
581
+ for (const descriptor of codexThreadHistoryModes(result))
582
+ if (expectedThreadId === undefined || descriptor.id === expectedThreadId)
583
+ this.threadHistoryModes.set(descriptor.id, descriptor.paginated);
584
+ }
585
+ readThreadHistoryMode(threadId) {
586
+ if (this.threadHistoryModes.has(threadId))
587
+ return Promise.resolve();
588
+ const existing = this.threadHistoryModeReads.get(threadId);
589
+ if (existing !== undefined)
590
+ return existing;
591
+ const read = this.client
592
+ .readThread(threadId)
593
+ .then((result) => {
594
+ if (this.threadHistoryModeReads.get(threadId) !== read)
595
+ return;
596
+ if (!this.threadHistoryModes.has(threadId))
597
+ this.observeThreadHistoryModes(result, threadId);
598
+ if (!this.threadHistoryModes.has(threadId))
599
+ this.threadHistoryModes.set(threadId, false);
600
+ })
601
+ .catch(() => {
602
+ // A capability probe must not make otherwise readable history unavailable.
603
+ if (this.threadHistoryModeReads.get(threadId) === read &&
604
+ !this.threadHistoryModes.has(threadId))
605
+ this.threadHistoryModes.set(threadId, false);
606
+ })
607
+ .finally(() => {
608
+ if (this.threadHistoryModeReads.get(threadId) === read)
609
+ this.threadHistoryModeReads.delete(threadId);
610
+ });
611
+ this.threadHistoryModeReads.set(threadId, read);
612
+ return read;
544
613
  }
545
614
  startThread(cwd, configuration, runId) {
546
- return this.clientFor(runId).startThread(cwd, configuration);
615
+ return this.clientFor(runId)
616
+ .startThread(cwd, configuration)
617
+ .then((result) => {
618
+ this.observeThreadHistoryModes(result);
619
+ return result;
620
+ });
547
621
  }
548
622
  forkThread(threadId, cwd, lastTurnId, configuration) {
549
623
  return this.client.forkThread(threadId, cwd, lastTurnId, configuration);
@@ -558,12 +632,41 @@ export class CodexRunner extends AbstractRunner {
558
632
  ? await client.startThread(session.cwd, configuration)
559
633
  : await client.forkThread(threadId, session.cwd, boundary, configuration);
560
634
  const forkedThreadId = extractId(result, 'thread');
635
+ this.observeThreadHistoryModes(result);
561
636
  await client.setThreadName(forkedThreadId, title);
562
637
  return forkedThreadId;
563
638
  });
564
639
  }
640
+ supportsRollback(session) {
641
+ return (session.externalSessionId !== null &&
642
+ this.threadHistoryModes.get(session.externalSessionId) === true);
643
+ }
644
+ async rollbackNative(session, boundary, targetTurnBoundary) {
645
+ void boundary;
646
+ if (!this.supportsRollback(session) || targetTurnBoundary === undefined)
647
+ throw new Error('SESSION_ROLLBACK_UNAVAILABLE');
648
+ const threadId = session.externalSessionId;
649
+ return this.withTemporaryClient(async (client) => {
650
+ const resumed = await client.resumeThread(threadId, session.cwd, {
651
+ model: session.model,
652
+ effort: session.effort,
653
+ access: session.access
654
+ });
655
+ if (!codexPaginatedThread(resumed)) {
656
+ this.threadHistoryModes.set(threadId, false);
657
+ throw new Error('SESSION_ROLLBACK_UNAVAILABLE');
658
+ }
659
+ await client.revertThread(threadId, targetTurnBoundary);
660
+ return Date.now();
661
+ });
662
+ }
565
663
  resumeThread(threadId, cwd, configuration, runId) {
566
- return this.clientFor(runId).resumeThread(threadId, cwd, configuration);
664
+ return this.clientFor(runId)
665
+ .resumeThread(threadId, cwd, configuration)
666
+ .then((result) => {
667
+ this.observeThreadHistoryModes(result, threadId);
668
+ return result;
669
+ });
567
670
  }
568
671
  startTurn(threadId, text, cwd, configuration, attachments, runId) {
569
672
  return this.clientFor(runId).startTurn(threadId, text, cwd, configuration, attachments);
@@ -199,6 +199,7 @@ export const computerUseToolDefinition = {
199
199
  additionalProperties: false
200
200
  },
201
201
  parallelSafety: 'serial',
202
+ allowedCallers: ['direct'],
202
203
  agentScope: 'ROOT_ONLY'
203
204
  };
204
205
  class ComputerUseRunSession {
@@ -30,16 +30,18 @@ export class MarAgentConversationNormalizer {
30
30
  }
31
31
  if (event.type === 'message.completed') {
32
32
  const modelName = record(event)?.modelName;
33
+ const itemId = this.messageItemId(event.itemId);
34
+ const payload = {
35
+ text: event.text,
36
+ format: 'markdown',
37
+ model: typeof modelName === 'string' ? modelName : null
38
+ };
33
39
  return [
34
40
  {
35
- itemId: this.messageItemId(event.itemId),
41
+ itemId,
36
42
  kind: 'assistant_message',
37
43
  status: 'COMPLETED',
38
- payload: {
39
- text: event.text,
40
- format: 'markdown',
41
- model: typeof modelName === 'string' ? modelName : null
42
- }
44
+ payload
43
45
  }
44
46
  ];
45
47
  }
@@ -61,12 +63,17 @@ export class MarAgentConversationNormalizer {
61
63
  payload: { sections: [{ index: 0, text: compactRunnerText(event.text, 10_000) }] }
62
64
  }
63
65
  ];
64
- if (event.type === 'tool.started')
65
- return this.#toolStarted(event);
66
- if (event.type === 'tool.completed')
67
- return this.#toolCompleted(event);
68
- if (event.type === 'tool.failed')
66
+ if (event.type === 'tool.started' ||
67
+ event.type === 'tool.completed' ||
68
+ event.type === 'tool.failed') {
69
+ if (event.parentCallId !== undefined)
70
+ return [];
71
+ if (event.type === 'tool.started')
72
+ return this.#toolStarted(event);
73
+ if (event.type === 'tool.completed')
74
+ return this.#toolCompleted(event);
69
75
  return this.#toolFailed(event);
76
+ }
70
77
  if (event.type === 'hosted_tool.updated') {
71
78
  const sourceSummary = event.summary.sources
72
79
  ?.slice(0, 20)
@@ -228,6 +228,7 @@ export class MarAgentHostedConversationRuntime {
228
228
  : undefined;
229
229
  const mcp = 'mcpServers' in input && input.mcpServers.length > 0
230
230
  ? await MarAgentMcpClientPool.connect(input.mcpServers, input.workspaceDirectory, {
231
+ authorizationScope: JSON.stringify([input.homeDirectory, input.sessionId]),
231
232
  processExecutor: input.processExecutor,
232
233
  owner: {
233
234
  sessionId: input.sessionId,
@@ -270,8 +271,8 @@ export class MarAgentHostedConversationRuntime {
270
271
  ...(mcp === undefined
271
272
  ? {}
272
273
  : {
273
- mcpTools: mcp.definitions(),
274
- callMcp: (call) => mcp.call(call.name, call.arguments)
274
+ mcpCatalog: () => mcp.catalog(),
275
+ callMcp: (call, signal, context) => mcp.call(call.name, call.arguments, signal, context?.expectedTool)
275
276
  }),
276
277
  allowUntrustedTls: false
277
278
  });
@@ -163,7 +163,8 @@ export class MarAgentManagedRunController {
163
163
  const mcpSecretEnvironment = parseSecretEnvironment(payload.mcpSecretEnvironment);
164
164
  mcpServers = this.runner.mcpConfiguration(payload.mcpInstallations, {
165
165
  ...secretEnvironment,
166
- ...mcpSecretEnvironment
166
+ ...mcpSecretEnvironment,
167
+ MAR_USERNAME: secretEnvironment['MAR_USERNAME'] ?? ''
167
168
  });
168
169
  if (mcpServers.some((server) => server.name === 'computer'))
169
170
  throw new Error('MAR_AGENT_MCP_RESERVED_NAMESPACE');
@@ -271,6 +272,7 @@ export class MarAgentManagedRunController {
271
272
  if (runtime.activeRunId !== undefined)
272
273
  throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
273
274
  runtime.activeRunId = input.runId;
275
+ await runtime.mcpLease.pool.refreshForExecution();
274
276
  const executionMcpServers = input.mcpServers.filter((server) => server.lifecycle === 'EXECUTION');
275
277
  computerUseBinding = await this.#computerUse?.bind({
276
278
  runId: input.runId,
@@ -290,7 +292,8 @@ export class MarAgentManagedRunController {
290
292
  }
291
293
  if (executionMcpServers.length > 0) {
292
294
  executionMcpLease = await this.#mcpClients.acquire(executionMcpServers, input.cwd, {
293
- reusable: false
295
+ reusable: false,
296
+ authorizationScope: runtime.session.id
294
297
  });
295
298
  }
296
299
  const userTools = executionMcpLease?.pool.definitions() ?? [];
@@ -311,11 +314,37 @@ export class MarAgentManagedRunController {
311
314
  skills: [...(imageExtensions.skills ?? []), computerUseHostSkill]
312
315
  }, {
313
316
  tools: [...nodeTools, ...userTools],
314
- call: async (call, signal) => {
317
+ catalog: () => {
318
+ const external = executionMcpLease?.pool.catalog() ?? {
319
+ revision: '',
320
+ servers: [],
321
+ tools: []
322
+ };
323
+ const computer = nodeTools.map((definition) => ({
324
+ definition,
325
+ serverId: 'computer',
326
+ rawName: definition.name,
327
+ sourceIdentity: `node:${input.runId}:computer`,
328
+ definitionRevision: createHash('sha256')
329
+ .update(JSON.stringify(definition))
330
+ .digest('hex'),
331
+ lifetime: 'execution',
332
+ exposure: 'direct'
333
+ }));
334
+ return {
335
+ revision: `${external.revision}:${input.runId}`,
336
+ servers: [
337
+ ...external.servers,
338
+ ...(computer.length ? [{ id: 'computer', name: 'Computer' }] : [])
339
+ ],
340
+ tools: [...external.tools, ...computer]
341
+ };
342
+ },
343
+ call: async (call, signal, context) => {
315
344
  if (computerUseBinding?.tools.some((tool) => tool.name === call.name))
316
345
  return computerUseBinding.call(call, signal);
317
346
  if (executionMcpLease?.pool.definitions().some((tool) => tool.name === call.name))
318
- return executionMcpLease.pool.call(call.name, call.arguments);
347
+ return executionMcpLease.pool.call(call.name, call.arguments, signal, context?.expectedTool);
319
348
  throw new Error('MAR_AGENT_MCP_TOOL_NOT_FOUND');
320
349
  }
321
350
  });
@@ -472,7 +501,11 @@ export class MarAgentManagedRunController {
472
501
  const dynamicCredentials = input.models.some((model) => model.credential?.type === 'RUNTIME_TICKET' ||
473
502
  model.imageGeneration?.credential?.type === 'RUNTIME_TICKET');
474
503
  const sharedMcpServers = input.mcpServers.filter((server) => server.lifecycle !== 'EXECUTION');
475
- const mcpLease = await this.#mcpClients.acquire(sharedMcpServers, input.cwd);
504
+ const mcpLease = await this.#mcpClients.acquire(sharedMcpServers, input.cwd, {
505
+ // Native Session IDs are assigned after the first lease; do not let that promotion
506
+ // change a confirmed actor's discovery identity when its warm runtime is recreated.
507
+ authorizationScope: input.secretEnvironment?.['MAR_USERNAME'] ? input.cwd : sessionKey
508
+ });
476
509
  let agent;
477
510
  let runtime;
478
511
  let models = [];
@@ -488,8 +521,8 @@ export class MarAgentManagedRunController {
488
521
  const runId = runtime?.activeRunId;
489
522
  return runId === undefined ? {} : this.requestQuestion(runId, request, signal);
490
523
  },
491
- mcpTools: mcp.definitions(),
492
- callMcp: async (call) => mcp.call(call.name, call.arguments),
524
+ mcpCatalog: () => mcp.catalog(),
525
+ callMcp: async (call, signal, context) => mcp.call(call.name, call.arguments, signal, context?.expectedTool),
493
526
  webSearch: (request, signal) => this.host.webSearch(request, signal),
494
527
  allowUntrustedTls: allowsUntrustedWebFetchTls(hostConfiguration.environment),
495
528
  resourceIdleTtlMs: this.#resourceIdleTtlMs
@@ -635,7 +668,12 @@ export class MarAgentManagedRunController {
635
668
  project(runId, event) {
636
669
  if (contextUsageChanged(event))
637
670
  this.host.refreshContext?.(runId);
638
- if (event.type === 'message.delta')
671
+ if (event.type === 'model.performance')
672
+ this.host.emit(runId, 'run.performance', {
673
+ latencyMs: event.latencyMs,
674
+ ...(event.tokensPerSecond === undefined ? {} : { tokensPerSecond: event.tokensPerSecond })
675
+ });
676
+ else if (event.type === 'message.delta')
639
677
  this.host.emit(runId, 'text.delta', {
640
678
  text: event.delta,
641
679
  phase: event.phase,