@myagentroam/node 0.9.161 → 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 (
|
|
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 =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
42
|
+
"@myagentroam/protocol": "0.9.163"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/ws": "^8.18.1"
|