@myagentroam/node 0.9.162 → 0.9.163

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.
@@ -118,27 +118,31 @@ export class ConversationHistoryService {
118
118
  async read(session, input) {
119
119
  const snapshotSequence = this.options.snapshotSequence();
120
120
  const readAt = Date.now();
121
+ const idleCursor = decodeIdleMarHistoryCursor(input.cursor, session.id);
122
+ if (idleCursor !== undefined) {
123
+ if (session.runner !== 'mar-agent')
124
+ throw new Error('EVENT_CURSOR_INVALID');
125
+ return this.readIdleMarAgentHistory(session, input, snapshotSequence, readAt);
126
+ }
121
127
  const activeCursor = decodeActiveHistoryCursor(input.cursor, session.id);
122
- if (activeCursor !== undefined ||
123
- this.options.hasManagedActiveRun(session) ||
124
- this.hasManagedRuntimeHistory(session))
128
+ if (activeCursor !== undefined || this.options.hasManagedActiveRun(session))
125
129
  return this.readManagedRuntime(session, input, activeCursor, snapshotSequence, readAt);
126
130
  return this.readHistory(session, input, snapshotSequence, readAt);
127
131
  }
128
132
  async readHistoryPage(session, input) {
129
- if (this.hasManagedRuntimeHistory(session)) {
133
+ if (input.cursor !== undefined)
134
+ return this.read(session, input);
135
+ if (this.options.hasManagedActiveRun(session) && this.hasManagedRuntimeHistory(session)) {
130
136
  const runtimeTurns = this.options.runtime.listConversationTurns({
131
137
  sessionId: session.id,
132
138
  limit: 500
133
139
  }).turns;
134
- const olderCursor = this.options.hasManagedActiveRun(session)
135
- ? {
136
- sessionId: session.id,
137
- stage: 'OFFICIAL',
138
- beforeAt: oldestTurnActivityAt(runtimeTurns),
139
- cursor: null
140
- }
141
- : undefined;
140
+ const olderCursor = {
141
+ sessionId: session.id,
142
+ stage: 'OFFICIAL',
143
+ beforeAt: oldestTurnActivityAt(runtimeTurns),
144
+ cursor: null
145
+ };
142
146
  return this.readManagedRuntime(session, input, olderCursor, this.options.snapshotSequence(), Date.now());
143
147
  }
144
148
  return this.readHistory(session, input, this.options.snapshotSequence(), Date.now());
@@ -151,6 +155,8 @@ export class ConversationHistoryService {
151
155
  0);
152
156
  }
153
157
  async readHistory(session, input, snapshotSequence, readAt) {
158
+ if (session.runner === 'mar-agent' && session.nativeControl === 'MAR_MANAGED')
159
+ return this.readIdleMarAgentHistory(session, input, snapshotSequence, readAt);
154
160
  let selected;
155
161
  try {
156
162
  selected = await this.readRunnerHistory(session, input);
@@ -290,6 +296,84 @@ export class ConversationHistoryService {
290
296
  throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
291
297
  return this.page(page.nextCursor, page.turns, { snapshotSequence, source: 'runtime', readAt });
292
298
  }
299
+ async readIdleMarAgentHistory(session, input, snapshotSequence, readAt) {
300
+ const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
301
+ const cursor = decodeIdleMarHistoryCursor(input.cursor, session.id);
302
+ const runtime = this.options.runtime.getAgentSession(session.id) === undefined
303
+ ? []
304
+ : this.options.runtime.listConversationTurns({ sessionId: session.id, limit: 500 }).turns;
305
+ const before = (turn) => {
306
+ const at = earliestTurnActivityAt(turn);
307
+ return cursor === undefined || at <= cursor.beforeAt;
308
+ };
309
+ let nativeCursor = cursor === undefined ? input.cursor : (cursor.nativeCursor ?? undefined);
310
+ let exhausted = false;
311
+ let locatingBoundary = cursor !== undefined;
312
+ const native = [];
313
+ let merged = [];
314
+ let lastInputCursor = nativeCursor;
315
+ const visited = new Set();
316
+ if (nativeCursor !== undefined)
317
+ visited.add(nativeCursor);
318
+ try {
319
+ for (;;) {
320
+ // Keep the containing SDK page as the anchor: a local-only failure has no
321
+ // native cursor, and equal timestamps require a stable Turn boundary.
322
+ lastInputCursor = nativeCursor;
323
+ const page = await this.readOfficial(session, {
324
+ limit,
325
+ ...(nativeCursor === undefined ? {} : { cursor: nativeCursor })
326
+ });
327
+ if (page === undefined)
328
+ throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
329
+ native.unshift(...page.turns);
330
+ exhausted = page.nextCursor === null;
331
+ const projected = native.map((turn) => {
332
+ const local = runtime.find((candidate) => sameConversationTurn(turn, candidate));
333
+ return local === undefined ? turn : mergeMarAgentRuntimeTurn(turn, local);
334
+ });
335
+ merged = projected.filter(before);
336
+ const oldestNative = oldestTurnActivityAt(native);
337
+ for (const turn of runtime) {
338
+ if (before(turn) &&
339
+ (exhausted || earliestTurnActivityAt(turn) >= oldestNative) &&
340
+ !native.some((candidate) => sameConversationTurn(candidate, turn)))
341
+ insertConversationTurnChronologically(merged, turn);
342
+ }
343
+ if (cursor !== undefined) {
344
+ const boundary = merged.findIndex((turn) => conversationTurnKey(turn) === cursor.beforeTurnKey);
345
+ if (boundary >= 0) {
346
+ merged = merged.slice(0, boundary);
347
+ locatingBoundary = false;
348
+ }
349
+ }
350
+ if ((!locatingBoundary && merged.length >= limit) || exhausted)
351
+ break;
352
+ if (visited.has(page.nextCursor))
353
+ throw new Error('EVENT_CURSOR_INVALID');
354
+ visited.add(page.nextCursor);
355
+ nativeCursor = page.nextCursor;
356
+ }
357
+ }
358
+ catch (error) {
359
+ // Never replace a visible local failure with an empty SDK snapshot.
360
+ if (input.cursor !== undefined || runtime.length === 0)
361
+ throw error;
362
+ return this.readManagedActive(session, input, undefined, snapshotSequence, readAt);
363
+ }
364
+ if (locatingBoundary)
365
+ throw new Error('EVENT_CURSOR_INVALID');
366
+ const turns = merged.slice(-limit);
367
+ const nextCursor = turns.length > 0 && (merged.length > turns.length || !exhausted)
368
+ ? encodeIdleMarHistoryCursor({
369
+ sessionId: session.id,
370
+ beforeAt: earliestTurnActivityAt(turns[0]),
371
+ beforeTurnKey: conversationTurnKey(turns[0]),
372
+ nativeCursor: lastInputCursor ?? null
373
+ })
374
+ : null;
375
+ return this.page(nextCursor, turns, { snapshotSequence, source: 'official', readAt });
376
+ }
293
377
  async readRunnerHistory(session, input) {
294
378
  const runner = this.options.runners.require(session.runner);
295
379
  let incompleteNative = false;
@@ -393,8 +477,7 @@ export class ConversationHistoryService {
393
477
  const codexActive = session.runner === 'codex' && this.options.hasManagedActiveRun(session);
394
478
  if (input.cursor !== undefined ||
395
479
  session.externalSessionId === null ||
396
- (!codexActive &&
397
- (this.options.hasManagedActiveRun(session) || !this.hasManagedRuntimeHistory(session))) ||
480
+ !codexActive ||
398
481
  !page.turns.some((turn) => turn.rewind === undefined &&
399
482
  terminalConversationTurn(turn.status) &&
400
483
  conversationClientMessageId(turn) !== null))
@@ -402,9 +485,7 @@ export class ConversationHistoryService {
402
485
  // A completed managed Turn keeps its live items and Run identity until Runtime cleanup.
403
486
  // Only its matched native Turn may supply a Rewind boundary.
404
487
  const historyInput = { limit: Math.min(500, Math.max(30, page.turns.length + 1)) };
405
- const authoritative = codexActive
406
- ? (await this.readRunnerHistory(session, historyInput).catch(() => undefined))?.page
407
- : await this.readOfficial(session, historyInput).catch(() => undefined);
488
+ const authoritative = (await this.readRunnerHistory(session, historyInput).catch(() => undefined))?.page;
408
489
  if (authoritative === undefined)
409
490
  return page;
410
491
  return {
@@ -509,6 +590,50 @@ function nativeHistoryPageCursor(cursor, sessionId) {
509
590
  return undefined;
510
591
  }
511
592
  }
593
+ const IDLE_MAR_HISTORY_CURSOR_PREFIX = 'mar-idle-history:1:';
594
+ function encodeIdleMarHistoryCursor(cursor) {
595
+ return `${IDLE_MAR_HISTORY_CURSOR_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString('base64url')}`;
596
+ }
597
+ function decodeIdleMarHistoryCursor(value, sessionId) {
598
+ if (value === undefined || !value.startsWith(IDLE_MAR_HISTORY_CURSOR_PREFIX))
599
+ return undefined;
600
+ try {
601
+ if (value.length > 1024)
602
+ throw new Error('EVENT_CURSOR_INVALID');
603
+ const decoded = JSON.parse(Buffer.from(value.slice(IDLE_MAR_HISTORY_CURSOR_PREFIX.length), 'base64url').toString('utf8'));
604
+ if (decoded.sessionId !== sessionId ||
605
+ typeof decoded.beforeAt !== 'number' ||
606
+ !Number.isFinite(decoded.beforeAt) ||
607
+ decoded.beforeAt < 0 ||
608
+ typeof decoded.beforeTurnKey !== 'string' ||
609
+ decoded.beforeTurnKey.length === 0 ||
610
+ decoded.beforeTurnKey.length > 256 ||
611
+ (decoded.nativeCursor !== null &&
612
+ (typeof decoded.nativeCursor !== 'string' || decoded.nativeCursor.length > 256)))
613
+ throw new Error('EVENT_CURSOR_INVALID');
614
+ return decoded;
615
+ }
616
+ catch {
617
+ throw new Error('EVENT_CURSOR_INVALID');
618
+ }
619
+ }
620
+ /** Retained MAR output owns its matched Turn, never the entire shared Session. */
621
+ function mergeMarAgentRuntimeTurn(native, local) {
622
+ const nativeErrors = local.items.some((item) => item.kind === 'error')
623
+ ? []
624
+ : native.items.filter((item) => item.kind === 'error');
625
+ const matchedInput = conversationClientMessageId(local) !== null &&
626
+ conversationClientMessageId(local) === conversationClientMessageId(native);
627
+ return {
628
+ ...local,
629
+ ...(matchedInput && native.rewind !== undefined ? { rewind: native.rewind } : {}),
630
+ status: nativeErrors.length > 0 ? 'FAILED' : local.status,
631
+ items: [
632
+ ...local.items,
633
+ ...nativeErrors.map((item) => ({ ...item, turnId: local.id, runId: local.runId }))
634
+ ]
635
+ };
636
+ }
512
637
  const ACTIVE_HISTORY_CURSOR_PREFIX = 'mar-active-history:1:';
513
638
  function encodeActiveHistoryCursor(cursor) {
514
639
  return `${ACTIVE_HISTORY_CURSOR_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString('base64url')}`;
@@ -606,6 +731,10 @@ function mergeRuntimeUserMetadata(officialTurn, runtimeTurn, attachments) {
606
731
  : item)
607
732
  };
608
733
  }
734
+ function conversationTurnKey(turn) {
735
+ const messageId = conversationClientMessageId(turn);
736
+ return messageId === null ? `turn:${turn.id}` : `message:${messageId}`;
737
+ }
609
738
  function sameConversationTurn(left, right) {
610
739
  if (left.id === right.id)
611
740
  return true;
@@ -1,7 +1,7 @@
1
1
  import { nodeCapabilitiesSchema } from '@myagentroam/protocol';
2
2
  const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
3
3
  const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
4
- const USER_AUTHORIZATION_CLOCK_SKEW_MS = 5_000;
4
+ const USER_AUTHORIZATION_CLOCK_SKEW_MS = 60 * 60 * 1_000;
5
5
  const NON_IMAGE_ATTACHMENT_PROMPT_PREFIX = '\n\nAttachments available in the Workspace:\n';
6
6
  const COMPOSER_IMAGE_MIME_TYPES = new Set([
7
7
  'image/png',
@@ -220,6 +220,7 @@ export function validUserAuthorization(value, envelope, operation, nodeId, now =
220
220
  typeof authorization.expiresAt === 'number' &&
221
221
  Number.isSafeInteger(authorization.issuedAt) &&
222
222
  Number.isSafeInteger(authorization.expiresAt) &&
223
+ authorization.expiresAt >= authorization.issuedAt &&
223
224
  authorization.issuedAt <= now + USER_AUTHORIZATION_CLOCK_SKEW_MS &&
224
225
  authorization.expiresAt >= now - USER_AUTHORIZATION_CLOCK_SKEW_MS &&
225
226
  authorization.expiresAt - authorization.issuedAt <= 15_000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.162",
3
+ "version": "0.9.163",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -36,10 +36,10 @@
36
36
  "playwright": "1.62.1",
37
37
  "ws": "^8.21.3",
38
38
  "zod": "4.4.3",
39
- "@myagentroam/agent": "0.9.162",
39
+ "@myagentroam/agent": "0.9.163",
40
40
  "@myagentroam/code-navigation": "0.9.129",
41
41
  "@myagentroam/marctl": "0.9.120",
42
- "@myagentroam/protocol": "0.9.162"
42
+ "@myagentroam/protocol": "0.9.163"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/ws": "^8.18.1"