@myagentroam/node 0.9.6 → 0.9.8
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.
- package/dist/connector.js +3 -1
- package/dist/runner/codex/conversation-parser.d.ts +1 -1
- package/dist/runner/codex/conversation-parser.js +25 -1
- package/dist/runner/codex/managed-run-controller.d.ts +2 -1
- package/dist/runner/codex/managed-run-controller.js +9 -1
- package/dist/runner/opencode/conversation-parser.js +5 -1
- package/dist/runtime-state.js +20 -7
- package/dist/service/run-event-service.js +10 -5
- package/dist/service/session-command-service.d.ts +1 -0
- package/dist/service/session-command-service.js +7 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +1 -0
- package/dist/service/workspace-queue-workbench-service.js +10 -0
- package/dist/util/runner-native-session-parsers.d.ts +2 -1
- package/dist/util/runner-native-session-parsers.js +28 -22
- package/package.json +2 -2
package/dist/connector.js
CHANGED
|
@@ -276,6 +276,7 @@ export class NodeConnector {
|
|
|
276
276
|
setCommandState: (sessionId, state) => this.commandStates.set(sessionId, state),
|
|
277
277
|
clearCommandState: (sessionId, commandId) => this.commandStates.clear(sessionId, commandId),
|
|
278
278
|
commandStates: (sessionId) => this.commandStates.list(sessionId),
|
|
279
|
+
hasActiveSessionLease: (sessionId) => this.runtime.hasActiveSessionLease(sessionId),
|
|
279
280
|
fast: (input) => this.executeNodeOperation('runner.command.execute', {
|
|
280
281
|
runner: 'codex',
|
|
281
282
|
commandId: 'fast',
|
|
@@ -489,7 +490,8 @@ export class NodeConnector {
|
|
|
489
490
|
projection: this.nativeSessionProjectionService,
|
|
490
491
|
resume: this.externalSessionResumeService,
|
|
491
492
|
profiles: () => this.runnerService.profiles(),
|
|
492
|
-
present: (session) => this.sessionPresentationService.present(session)
|
|
493
|
+
present: (session) => this.sessionPresentationService.present(session),
|
|
494
|
+
resumeQueueAfterCommandRun: (sessionId) => this.workspaceQueueWorkbenchService.resumeAfterCommandRun(sessionId)
|
|
493
495
|
});
|
|
494
496
|
this.sessionMessageService = new SessionMessageService({
|
|
495
497
|
runtime: this.runtime,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NodeAgentSession, NodeConversationItem, NodeConversationTurn, NodeMessageAttachmentInput } from '../../database.js';
|
|
2
2
|
export type PublicConversationItem = {
|
|
3
3
|
readonly itemId: string;
|
|
4
|
-
readonly kind: 'reasoning_summary' | 'tool_call' | 'command_execution' | 'user_input_request' | 'plan' | 'context_compaction' | 'file_change' | 'error' | 'unknown';
|
|
4
|
+
readonly kind: 'assistant_message' | 'reasoning_summary' | 'tool_call' | 'command_execution' | 'user_input_request' | 'plan' | 'context_compaction' | 'file_change' | 'error' | 'unknown';
|
|
5
5
|
readonly status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'DECLINED';
|
|
6
6
|
readonly payload: Record<string, unknown>;
|
|
7
7
|
readonly merge?: boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
2
2
|
const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
|
|
3
|
+
const MAX_ASSISTANT_MESSAGE_CHARACTERS = 64 * 1024;
|
|
3
4
|
export * from '../../util/runner-native-session-parsers.js';
|
|
4
5
|
export function codexOfficialConversationPage(session, result, input, managedTurnForNative) {
|
|
5
6
|
if (!isPlainRecord(result) || !isPlainRecord(result.thread))
|
|
@@ -43,7 +44,7 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
|
|
|
43
44
|
const officialNativeItemIds = codexNativeItemIds(value.items);
|
|
44
45
|
const officialAssistantCount = items.filter((item) => item.kind === 'assistant_message').length;
|
|
45
46
|
const officialAssistantMessage = officialAssistantCount > 0;
|
|
46
|
-
const managedAssistants = managedTurn.items.filter((item) => item.kind === 'assistant_message');
|
|
47
|
+
const managedAssistants = managedTurn.items.filter((item) => item.kind === 'assistant_message' && conversationItemNativeId(item.id) === undefined);
|
|
47
48
|
const managedAssistantCandidates = officialAssistantCount === 0 ? [] : managedAssistants.slice(-officialAssistantCount);
|
|
48
49
|
const assistantLength = (values) => values.reduce((length, item) => {
|
|
49
50
|
if (item.kind !== 'assistant_message' || !isPlainRecord(item.payload))
|
|
@@ -108,6 +109,15 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
|
|
|
108
109
|
replacement = officialAssistants.find((item) => !consumedOfficialIds.has(item.id));
|
|
109
110
|
}
|
|
110
111
|
if (replacement !== undefined) {
|
|
112
|
+
if (managedItem.kind === 'assistant_message' &&
|
|
113
|
+
replacement.kind === 'assistant_message' &&
|
|
114
|
+
(assistantLength([managedItem]) > assistantLength([replacement]) ||
|
|
115
|
+
((managedTurn.completedAt ?? 0) > (completedAt ?? 0) &&
|
|
116
|
+
assistantLength([managedItem]) > 0))) {
|
|
117
|
+
reconciledItems.push(managedItem);
|
|
118
|
+
consumedOfficialIds.add(replacement.id);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
111
121
|
if (!consumedOfficialIds.has(replacement.id)) {
|
|
112
122
|
reconciledItems.push(replacement);
|
|
113
123
|
consumedOfficialIds.add(replacement.id);
|
|
@@ -504,6 +514,20 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
|
|
|
504
514
|
status: completed ? 'COMPLETED' : 'IN_PROGRESS',
|
|
505
515
|
payload: { explanation: compactRunnerText(planText, 20_000), steps: [] }
|
|
506
516
|
};
|
|
517
|
+
if (value.type === 'agentMessage') {
|
|
518
|
+
if (!completed || typeof value.text !== 'string' || value.text.length === 0)
|
|
519
|
+
return undefined;
|
|
520
|
+
return {
|
|
521
|
+
itemId,
|
|
522
|
+
kind: 'assistant_message',
|
|
523
|
+
status: 'COMPLETED',
|
|
524
|
+
payload: {
|
|
525
|
+
text: compactRunnerText(value.text, MAX_ASSISTANT_MESSAGE_CHARACTERS),
|
|
526
|
+
format: 'markdown',
|
|
527
|
+
model: null
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
}
|
|
507
531
|
if (value.type === 'commandExecution') {
|
|
508
532
|
const output = typeof value.aggregatedOutput === 'string' ? value.aggregatedOutput : '';
|
|
509
533
|
return {
|
|
@@ -53,7 +53,7 @@ export interface CodexManagedRunHost<TAttachment> {
|
|
|
53
53
|
}): void;
|
|
54
54
|
emitItem(runId: string, item: {
|
|
55
55
|
readonly itemId: string;
|
|
56
|
-
readonly kind: 'reasoning_summary' | 'tool_call' | 'command_execution' | 'user_input_request' | 'plan' | 'context_compaction' | 'file_change' | 'error' | 'unknown';
|
|
56
|
+
readonly kind: 'reasoning_summary' | 'tool_call' | 'command_execution' | 'user_input_request' | 'assistant_message' | 'plan' | 'context_compaction' | 'file_change' | 'error' | 'unknown';
|
|
57
57
|
readonly status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED' | 'DECLINED';
|
|
58
58
|
readonly payload: Record<string, unknown>;
|
|
59
59
|
readonly merge?: boolean;
|
|
@@ -65,6 +65,7 @@ export interface CodexManagedRunHost<TAttachment> {
|
|
|
65
65
|
setCommandState(sessionId: string, state: RunnerCommandState): void;
|
|
66
66
|
clearCommandState(sessionId: string, commandId: string): void;
|
|
67
67
|
commandStates(sessionId: string): readonly RunnerCommandState[];
|
|
68
|
+
hasActiveSessionLease(sessionId: string): boolean;
|
|
68
69
|
fast(input: string): Promise<unknown>;
|
|
69
70
|
}
|
|
70
71
|
export declare class CodexManagedRunController<TAttachment> {
|
|
@@ -80,6 +80,8 @@ export class CodexManagedRunController {
|
|
|
80
80
|
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
81
81
|
}
|
|
82
82
|
async compact(session, threadId, runId) {
|
|
83
|
+
if (this.host.hasActiveSessionLease(session.id))
|
|
84
|
+
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
83
85
|
this.host.adopt({
|
|
84
86
|
runId,
|
|
85
87
|
sessionId: session.id,
|
|
@@ -153,7 +155,13 @@ export class CodexManagedRunController {
|
|
|
153
155
|
return;
|
|
154
156
|
}
|
|
155
157
|
if (notification.method === 'item/agentMessage/delta' && typeof params.delta === 'string') {
|
|
156
|
-
|
|
158
|
+
const itemId = typeof params.itemId === 'string'
|
|
159
|
+
? boundedConversationItemId('codex', params.itemId)
|
|
160
|
+
: undefined;
|
|
161
|
+
this.host.emit(runId, 'text.delta', {
|
|
162
|
+
text: params.delta,
|
|
163
|
+
...(itemId === undefined ? {} : { itemId })
|
|
164
|
+
});
|
|
157
165
|
return;
|
|
158
166
|
}
|
|
159
167
|
if (notification.method === 'item/reasoning/summaryTextDelta' &&
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { boundedConversationItemId, compactRunnerText, compactRunnerValue } from '../codex/conversation-parser.js';
|
|
2
|
+
import { runtimeAssistantProjectionMoreComplete } from '../../util/runner-native-session-parsers.js';
|
|
2
3
|
export function openCodeConversationPage(session, messages, input, managedTurn) {
|
|
3
4
|
const records = messages.flatMap((value) => {
|
|
4
5
|
const entry = record(value);
|
|
@@ -63,6 +64,9 @@ function openCodeTurn(session, user, userParts, assistants, sequence, managed) {
|
|
|
63
64
|
const completedAt = Math.max(startedAt ?? 0, ...assistants.map(({ info }) => number(record(info.time)?.completed) ?? 0)) || startedAt;
|
|
64
65
|
const id = managed?.id ?? `${session.id}:official:${nativeTurnId}`;
|
|
65
66
|
const runId = managed?.runId ?? null;
|
|
67
|
+
const projectedItems = runtimeAssistantProjectionMoreComplete(managed, items)
|
|
68
|
+
? managed.items
|
|
69
|
+
: items;
|
|
66
70
|
return {
|
|
67
71
|
id,
|
|
68
72
|
sessionId: session.id,
|
|
@@ -72,7 +76,7 @@ function openCodeTurn(session, user, userParts, assistants, sequence, managed) {
|
|
|
72
76
|
model: assistants.find(({ info }) => typeof info.modelID === 'string')?.info.modelID ?? null,
|
|
73
77
|
effort: managed?.effort ?? null,
|
|
74
78
|
access: managed?.access ?? null,
|
|
75
|
-
items:
|
|
79
|
+
items: projectedItems.map((value) => ({ ...value, turnId: id, runId })),
|
|
76
80
|
startedAt,
|
|
77
81
|
completedAt
|
|
78
82
|
};
|
package/dist/runtime-state.js
CHANGED
|
@@ -630,7 +630,13 @@ export class NodeRuntimeState {
|
|
|
630
630
|
}
|
|
631
631
|
this.runs.delete(runId);
|
|
632
632
|
this.events.delete(runId);
|
|
633
|
-
const turns = this.turns.get(run.sessionId) ?? []
|
|
633
|
+
const turns = (this.turns.get(run.sessionId) ?? []).filter((turn) => {
|
|
634
|
+
const hasVisibleError = turn.items.some((item) => item.kind === 'error');
|
|
635
|
+
if (turn.runId === runId)
|
|
636
|
+
return run.status === 'SUCCEEDED' || run.status === 'INTERRUPTED' || hasVisibleError;
|
|
637
|
+
const isTransientErrorTurn = hasVisibleError && turn.status !== 'SUCCEEDED' && turn.status !== 'INTERRUPTED';
|
|
638
|
+
return run.status !== 'SUCCEEDED' || !isTransientErrorTurn;
|
|
639
|
+
});
|
|
634
640
|
const completed = turns.filter((turn) => terminalTurn(turn.status));
|
|
635
641
|
const retainedCompleted = new Set(completed.slice(-NodeRuntimeState.MAX_COMPLETED_TURNS_PER_SESSION).map((turn) => turn.id));
|
|
636
642
|
this.turns.set(run.sessionId, turns.filter((turn) => !terminalTurn(turn.status) || retainedCompleted.has(turn.id)));
|
|
@@ -742,13 +748,19 @@ export class NodeRuntimeState {
|
|
|
742
748
|
if (event.eventType === 'text.delta' &&
|
|
743
749
|
isRecord(event.payload) &&
|
|
744
750
|
typeof event.payload.text === 'string') {
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
? lastItem
|
|
751
|
+
const nativeItemId = typeof event.payload.itemId === 'string' && event.payload.itemId.length <= 88
|
|
752
|
+
? event.payload.itemId
|
|
748
753
|
: undefined;
|
|
754
|
+
const stableItemId = nativeItemId === undefined ? undefined : `${event.runId}:${nativeItemId}`;
|
|
755
|
+
const lastItem = turn.items.at(-1);
|
|
756
|
+
const existing = stableItemId === undefined
|
|
757
|
+
? lastItem?.kind === 'assistant_message' && activeConversationItem(lastItem.status)
|
|
758
|
+
? lastItem
|
|
759
|
+
: undefined
|
|
760
|
+
: turn.items.find((item) => item.id === stableItemId && item.kind === 'assistant_message');
|
|
749
761
|
const text = `${existing === undefined ? '' : readableText(existing.payload)}${event.payload.text}`;
|
|
750
762
|
const item = {
|
|
751
|
-
id: existing?.id ?? `${event.runId}:assistant:${event.sequence}`,
|
|
763
|
+
id: existing?.id ?? stableItemId ?? `${event.runId}:assistant:${event.sequence}`,
|
|
752
764
|
sessionId: run.sessionId,
|
|
753
765
|
turnId: turn.id,
|
|
754
766
|
runId: event.runId,
|
|
@@ -829,7 +841,7 @@ function terminal(status) {
|
|
|
829
841
|
return ['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED', 'LOST'].includes(status);
|
|
830
842
|
}
|
|
831
843
|
function terminalTurn(status) {
|
|
832
|
-
return ['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED'].includes(status);
|
|
844
|
+
return ['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED', 'LOST'].includes(status);
|
|
833
845
|
}
|
|
834
846
|
function transitionAllowed(from, to) {
|
|
835
847
|
return !terminal(from) || from === to;
|
|
@@ -869,7 +881,8 @@ function conversationItemFromEvent(event) {
|
|
|
869
881
|
};
|
|
870
882
|
}
|
|
871
883
|
function projectableConversationKind(value) {
|
|
872
|
-
return (value === '
|
|
884
|
+
return (value === 'assistant_message' ||
|
|
885
|
+
value === 'reasoning_summary' ||
|
|
873
886
|
value === 'tool_call' ||
|
|
874
887
|
value === 'command_execution' ||
|
|
875
888
|
value === 'user_input_request' ||
|
|
@@ -14,7 +14,7 @@ export class RunEventService {
|
|
|
14
14
|
status === undefined &&
|
|
15
15
|
isPlainRecord(payload) &&
|
|
16
16
|
typeof payload.text === 'string') {
|
|
17
|
-
this.enqueueText(runId, payload.text);
|
|
17
|
+
this.enqueueText(runId, payload.text, typeof payload.itemId === 'string' ? payload.itemId : undefined);
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
this.flushText(runId);
|
|
@@ -28,18 +28,20 @@ export class RunEventService {
|
|
|
28
28
|
flushPendingText(runId) {
|
|
29
29
|
this.flushText(runId);
|
|
30
30
|
}
|
|
31
|
-
enqueueText(runId, text) {
|
|
31
|
+
enqueueText(runId, text, itemId) {
|
|
32
32
|
if (text.length === 0)
|
|
33
33
|
return;
|
|
34
34
|
const pending = this.pendingText.get(runId);
|
|
35
|
-
if (pending !== undefined) {
|
|
35
|
+
if (pending !== undefined && pending.itemId === itemId) {
|
|
36
36
|
pending.text += text;
|
|
37
37
|
if (pending.text.length >= MAX_BATCH_CHARS)
|
|
38
38
|
this.flushText(runId);
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
+
if (pending !== undefined)
|
|
42
|
+
this.flushText(runId);
|
|
41
43
|
const timer = setTimeout(() => this.flushText(runId), COALESCE_MS);
|
|
42
|
-
this.pendingText.set(runId, { text, timer });
|
|
44
|
+
this.pendingText.set(runId, { text, ...(itemId === undefined ? {} : { itemId }), timer });
|
|
43
45
|
}
|
|
44
46
|
flushText(runId) {
|
|
45
47
|
const pending = this.pendingText.get(runId);
|
|
@@ -47,7 +49,10 @@ export class RunEventService {
|
|
|
47
49
|
return;
|
|
48
50
|
clearTimeout(pending.timer);
|
|
49
51
|
this.pendingText.delete(runId);
|
|
50
|
-
this.emitNow(runId, 'text.delta', {
|
|
52
|
+
this.emitNow(runId, 'text.delta', {
|
|
53
|
+
text: pending.text,
|
|
54
|
+
...(pending.itemId === undefined ? {} : { itemId: pending.itemId })
|
|
55
|
+
});
|
|
51
56
|
}
|
|
52
57
|
emitNow(runId, eventType, payload, status) {
|
|
53
58
|
if (this.options.runtime.getRun(runId) === undefined)
|
|
@@ -12,6 +12,7 @@ interface SessionCommandServiceOptions {
|
|
|
12
12
|
readonly resume: ExternalSessionResumeService;
|
|
13
13
|
readonly profiles: () => readonly RunnerProfile[];
|
|
14
14
|
readonly present: (session: NodeAgentSession) => unknown;
|
|
15
|
+
readonly resumeQueueAfterCommandRun: (sessionId: string) => void;
|
|
15
16
|
}
|
|
16
17
|
export declare class SessionCommandService {
|
|
17
18
|
private readonly options;
|
|
@@ -48,6 +48,9 @@ export class SessionCommandService {
|
|
|
48
48
|
?.commands.find((candidate) => isPlainRecord(candidate) && candidate.id === input.commandId);
|
|
49
49
|
if (command?.available !== true)
|
|
50
50
|
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
51
|
+
if (command.interaction === 'IMMEDIATE_ACTION' &&
|
|
52
|
+
this.options.runtime.hasActiveSessionLease(session.id))
|
|
53
|
+
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
51
54
|
const invocation = parseCommandInvocation({ descriptor: command }, input.input);
|
|
52
55
|
const secretEnvironment = parseSecretEnvironment(input.secretEnvironment);
|
|
53
56
|
const runner = this.options.runners.product(session.runner);
|
|
@@ -56,6 +59,10 @@ export class SessionCommandService {
|
|
|
56
59
|
const result = await runner.executeCommand(session, command, invocation.input, {
|
|
57
60
|
secretEnvironment
|
|
58
61
|
});
|
|
62
|
+
if (command.interaction === 'IMMEDIATE_ACTION' &&
|
|
63
|
+
isPlainRecord(result) &&
|
|
64
|
+
isPlainRecord(result.run))
|
|
65
|
+
this.options.resumeQueueAfterCommandRun(session.id);
|
|
59
66
|
return {
|
|
60
67
|
...(isPlainRecord(result) ? result : { result }),
|
|
61
68
|
session: this.options.present(session)
|
|
@@ -66,6 +66,7 @@ export declare class WorkspaceQueueWorkbenchService<TAttachment> {
|
|
|
66
66
|
migrateSession(previousId: string, nextId: string): void;
|
|
67
67
|
cancelQueued(runId: string): void;
|
|
68
68
|
pauseForInterrupt(sessionId: string): void;
|
|
69
|
+
resumeAfterCommandRun(sessionId: string): void;
|
|
69
70
|
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
70
71
|
private get;
|
|
71
72
|
private pauseOrResume;
|
|
@@ -64,6 +64,16 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
64
64
|
this.options.runtime.pauseSessionQueue(session.id);
|
|
65
65
|
this.publish(session.id);
|
|
66
66
|
}
|
|
67
|
+
resumeAfterCommandRun(sessionId) {
|
|
68
|
+
const session = this.options.runtime.getAgentSession(sessionId);
|
|
69
|
+
if (session === undefined)
|
|
70
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
71
|
+
if (!this.options.runtime.sessionQueue(session.id).paused)
|
|
72
|
+
return;
|
|
73
|
+
this.options.runtime.resumeSessionQueue(session.id);
|
|
74
|
+
this.publish(session.id);
|
|
75
|
+
this.schedule(session.id);
|
|
76
|
+
}
|
|
67
77
|
operations() {
|
|
68
78
|
return {
|
|
69
79
|
'workspace.queue.get': (data) => this.get(record(data)),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SessionActivityState } from '@myagentroam/protocol';
|
|
2
|
-
import type { NodeAgentSession, NodeConversationTurn } from '../database.js';
|
|
2
|
+
import type { NodeAgentSession, NodeConversationItem, NodeConversationTurn } from '../database.js';
|
|
3
3
|
import type { NativeSessionHistory, NativeTranscriptImageAttachment } from '../native-session-history.js';
|
|
4
4
|
export interface SessionContextUsage {
|
|
5
5
|
readonly model: string | null;
|
|
@@ -46,6 +46,7 @@ export declare function nativeConversationPage(session: NodeAgentSession, histor
|
|
|
46
46
|
readonly turns: readonly NodeConversationTurn[];
|
|
47
47
|
readonly nextCursor: string | null;
|
|
48
48
|
};
|
|
49
|
+
export declare function runtimeAssistantProjectionMoreComplete(runtimeTurn: NodeConversationTurn | undefined, projectedItems: readonly NodeConversationItem[]): boolean;
|
|
49
50
|
/**
|
|
50
51
|
* Claude JSONL does not retain browser request IDs. While the Node process is alive, match current
|
|
51
52
|
* runtime user messages by content and creation time so native history replaces the optimistic Turn.
|
|
@@ -156,19 +156,6 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
156
156
|
const effectiveCursor = history.cursorReset === true ? undefined : input.cursor;
|
|
157
157
|
const nativeTurns = nativeTranscriptTurns(history.items);
|
|
158
158
|
const nativeTurnIds = nativeTranscriptTurnIds(session.id, nativeTurns);
|
|
159
|
-
const representedClientMessageIds = new Set(nativeTurns.flatMap((entries) => entries.flatMap(({ entry, index }) => {
|
|
160
|
-
if (entry.kind !== 'message' || entry.role !== 'USER')
|
|
161
|
-
return [];
|
|
162
|
-
const clientMessageId = nativeUserClientMessageId(entry, entry.createdAt ?? index, runtimeTurns);
|
|
163
|
-
return clientMessageId === null ? [] : [clientMessageId];
|
|
164
|
-
})));
|
|
165
|
-
const missingRuntimeErrorTurns = effectiveCursor === undefined
|
|
166
|
-
? runtimeTurns.filter((turn) => {
|
|
167
|
-
const clientMessageId = runtimeTurnClientMessageId(turn);
|
|
168
|
-
return (turn.items.some((item) => item.kind === 'error') &&
|
|
169
|
-
(clientMessageId === undefined || !representedClientMessageIds.has(clientMessageId)));
|
|
170
|
-
})
|
|
171
|
-
: [];
|
|
172
159
|
const end = nativePageEnd(effectiveCursor, session.id, nativeTurns.length);
|
|
173
160
|
const start = Math.max(0, end - limit);
|
|
174
161
|
const nativePageTurns = nativeTurns
|
|
@@ -190,10 +177,16 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
190
177
|
const runtimeErrors = (runtimeTurn?.items ?? [])
|
|
191
178
|
.filter((item) => item.kind === 'error')
|
|
192
179
|
.map((item) => ({ ...item, turnId, runId: runtimeTurn?.runId ?? null }));
|
|
193
|
-
const items =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
180
|
+
const items = runtimeAssistantProjectionMoreComplete(runtimeTurn, nativeItems)
|
|
181
|
+
? runtimeTurn.items.map((item) => ({
|
|
182
|
+
...item,
|
|
183
|
+
turnId,
|
|
184
|
+
runId: runtimeTurn.runId
|
|
185
|
+
}))
|
|
186
|
+
: [
|
|
187
|
+
...nativeItems,
|
|
188
|
+
...runtimeErrors.filter((error) => !nativeItems.some((item) => item.id === error.id))
|
|
189
|
+
];
|
|
197
190
|
const startedAt = items[0]?.startedAt ?? null;
|
|
198
191
|
const completedAt = items.at(-1)?.completedAt ?? startedAt;
|
|
199
192
|
const after = lastNativeItemId(entries);
|
|
@@ -219,11 +212,8 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
219
212
|
completedAt
|
|
220
213
|
};
|
|
221
214
|
});
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const turns = combined.slice(-limit);
|
|
225
|
-
const nativePageIds = new Set(nativePageTurns.map((turn) => turn.id));
|
|
226
|
-
const nextEnd = start + dropped.filter((turn) => nativePageIds.has(turn.id)).length;
|
|
215
|
+
const turns = nativePageTurns;
|
|
216
|
+
const nextEnd = start;
|
|
227
217
|
const cursorBase = {
|
|
228
218
|
sessionId: session.id,
|
|
229
219
|
...(history.snapshotEnd === undefined ? {} : { snapshotEnd: history.snapshotEnd }),
|
|
@@ -253,6 +243,22 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
|
|
|
253
243
|
: precedingWindow
|
|
254
244
|
};
|
|
255
245
|
}
|
|
246
|
+
export function runtimeAssistantProjectionMoreComplete(runtimeTurn, projectedItems) {
|
|
247
|
+
if (runtimeTurn === undefined)
|
|
248
|
+
return false;
|
|
249
|
+
const assistantStats = (items) => items.reduce((stats, item) => {
|
|
250
|
+
if (item.kind !== 'assistant_message' || !isPlainRecord(item.payload))
|
|
251
|
+
return stats;
|
|
252
|
+
return {
|
|
253
|
+
count: stats.count + 1,
|
|
254
|
+
characters: stats.characters +
|
|
255
|
+
(typeof item.payload.text === 'string' ? item.payload.text.length : 0)
|
|
256
|
+
};
|
|
257
|
+
}, { count: 0, characters: 0 });
|
|
258
|
+
const runtime = assistantStats(runtimeTurn.items);
|
|
259
|
+
const projected = assistantStats(projectedItems);
|
|
260
|
+
return runtime.count > projected.count || runtime.characters > projected.characters;
|
|
261
|
+
}
|
|
256
262
|
function runtimeTurnClientMessageId(turn) {
|
|
257
263
|
const user = turn.items.find((item) => item.kind === 'user_message');
|
|
258
264
|
return isPlainRecord(user?.payload) && typeof user.payload.clientMessageId === 'string'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"node-pty": "1.1.0",
|
|
25
25
|
"ws": "^8.21.3",
|
|
26
26
|
"zod": "4.4.3",
|
|
27
|
-
"@myagentroam/protocol": "^0.9.
|
|
27
|
+
"@myagentroam/protocol": "^0.9.8"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/ws": "^8.18.1"
|