@myagentroam/node 0.9.105 → 0.9.107

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 CHANGED
@@ -856,6 +856,7 @@ export class NodeConnector {
856
856
  runtime: this.runtime,
857
857
  runners: this.runners,
858
858
  projection: this.nativeSessionProjectionService,
859
+ resolveForCommand: (sessionId) => this.externalSessionResumeService.resolveForMessage(sessionId),
859
860
  profiles: () => this.runnerService.profiles(),
860
861
  present: (session) => this.sessionPresentationService.present(session),
861
862
  resumeQueueAfterCommandRun: (sessionId) => this.workspaceQueueWorkbenchService.resumeAfterCommandRun(sessionId),
@@ -43,21 +43,17 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
43
43
  if (items.length === 0)
44
44
  return undefined;
45
45
  if (managedTurn !== undefined) {
46
+ const officialComplete = value.status === 'completed' ||
47
+ value.status === 'failed' ||
48
+ value.status === 'interrupted' ||
49
+ (value.status === undefined && codexTimestamp(value.completedAt) !== null);
46
50
  const localUser = managedTurn.items.find((item) => item.kind === 'user_message');
47
51
  const officialNativeItemIds = codexNativeItemIds(value.items);
48
52
  const officialAssistantCount = items.filter((item) => item.kind === 'assistant_message').length;
49
53
  const officialAssistantMessage = officialAssistantCount > 0;
50
54
  const managedAssistants = managedTurn.items.filter((item) => item.kind === 'assistant_message' && conversationItemNativeId(item.id) === undefined);
51
55
  const managedAssistantCandidates = officialAssistantCount === 0 ? [] : managedAssistants.slice(-officialAssistantCount);
52
- const assistantLength = (values) => values.reduce((length, item) => {
53
- if (item.kind !== 'assistant_message' || !isPlainRecord(item.payload))
54
- return length;
55
- return length + (typeof item.payload.text === 'string' ? item.payload.text.length : 0);
56
- }, 0);
57
- const preferLocalAssistant = officialAssistantMessage &&
58
- (assistantLength(managedAssistantCandidates) > assistantLength(items) ||
59
- ((managedTurn.completedAt ?? 0) > (completedAt ?? 0) &&
60
- assistantLength(managedAssistantCandidates) > 0));
56
+ const preferLocalAssistant = officialAssistantMessage && !officialComplete;
61
57
  const replacedManagedAssistantIds = new Set(preferLocalAssistant
62
58
  ? []
63
59
  : officialAssistantCount === 0
@@ -66,6 +62,8 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
66
62
  const localLiveItems = managedTurn.items.filter((item) => {
67
63
  if (item.kind === 'user_message')
68
64
  return false;
65
+ if (preferLocalAssistant && item.kind === 'assistant_message')
66
+ return true;
69
67
  // `item/agentMessage/delta` uses a synthetic runtime ID. Once
70
68
  // `thread/read` returns the final Agent message, it is the same reply
71
69
  // without a shared native item ID and must replace that transient item.
@@ -112,15 +110,6 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
112
110
  replacement = officialAssistants.find((item) => !consumedOfficialIds.has(item.id));
113
111
  }
114
112
  if (replacement !== undefined) {
115
- if (managedItem.kind === 'assistant_message' &&
116
- replacement.kind === 'assistant_message' &&
117
- (assistantLength([managedItem]) > assistantLength([replacement]) ||
118
- ((managedTurn.completedAt ?? 0) > (completedAt ?? 0) &&
119
- assistantLength([managedItem]) > 0))) {
120
- reconciledItems.push(managedItem);
121
- consumedOfficialIds.add(replacement.id);
122
- continue;
123
- }
124
113
  if (!consumedOfficialIds.has(replacement.id)) {
125
114
  reconciledItems.push(replacement);
126
115
  consumedOfficialIds.add(replacement.id);
@@ -134,13 +123,13 @@ export function codexOfficialTurn(session, value, sequence, managedTurn) {
134
123
  reconciledItems.push(item);
135
124
  return {
136
125
  ...managedTurn,
137
- status: 'SUCCEEDED',
126
+ status: officialComplete ? 'SUCCEEDED' : managedTurn.status,
138
127
  // Reconcile against the managed runtime order. Official items replace
139
128
  // matching live projections in place; newly visible terminal items such
140
129
  // as a plan are appended after the commentary and tools that preceded it.
141
130
  items: reconciledItems,
142
131
  startedAt,
143
- completedAt
132
+ completedAt: officialComplete ? completedAt : null
144
133
  };
145
134
  }
146
135
  return {
@@ -20,7 +20,7 @@ export function projectMarAgentHistory(session, entries, options = {}) {
20
20
  rewind: { before: previous?.id ?? null, after: turnId },
21
21
  items: [],
22
22
  startedAt: at,
23
- completedAt: at
23
+ completedAt: null
24
24
  };
25
25
  turns.set(turnId, created);
26
26
  return created;
@@ -32,13 +32,17 @@ export function projectMarAgentHistory(session, entries, options = {}) {
32
32
  items.push(item);
33
33
  else
34
34
  items[index] = { ...items[index], ...item, startedAt: items[index].startedAt };
35
- turns.set(turn.id, { ...turn, items, completedAt: item.completedAt ?? turn.completedAt });
35
+ turns.set(turn.id, { ...turn, items });
36
36
  };
37
37
  for (const entry of entries) {
38
38
  if (entry.kind === 'user') {
39
39
  const at = Date.parse(entry.timestamp);
40
40
  const turn = ensure(entry.turnId, at);
41
- const id = `${entry.turnId}:user`;
41
+ // SDK mailbox messages are persisted as additional turn.user records in the same Turn.
42
+ // The first input keeps its existing identity; later inputs use their storage sequence.
43
+ const id = turn.userItemId === null
44
+ ? `${entry.turnId}:user`
45
+ : `${entry.turnId}:user:${entry.sequence}`;
42
46
  const images = entry.images;
43
47
  const registeredImages = images === undefined ? [] : (options.registerInputImages?.(entry) ?? []);
44
48
  replace(turn, historyItem(session, turn, id, 'user_message', 'COMPLETED', {
@@ -56,7 +60,8 @@ export function projectMarAgentHistory(session, entries, options = {}) {
56
60
  }
57
61
  : {})
58
62
  }, at));
59
- turns.set(entry.turnId, { ...turns.get(entry.turnId), userItemId: id });
63
+ if (turn.userItemId === null)
64
+ turns.set(entry.turnId, { ...turns.get(entry.turnId), userItemId: id });
60
65
  continue;
61
66
  }
62
67
  const event = entry.event;
@@ -1,5 +1,4 @@
1
1
  import { boundedConversationItemId, compactRunnerText } from '../codex/conversation-parser.js';
2
- import { runtimeAssistantProjectionMoreComplete } from '../../util/runner-native-session-parsers.js';
3
2
  import { stripMissionStartPrompt } from '../../service/mission-runtime.js';
4
3
  import { normalizeOpenCodeTool } from './tool-normalizer.js';
5
4
  export function openCodeConversationPage(session, messages, input, managedTurn) {
@@ -73,9 +72,14 @@ function openCodeTurn(session, user, userParts, assistants, sequence, managed) {
73
72
  const completedAt = Math.max(startedAt ?? 0, ...assistants.map(({ info }) => number(record(info.time)?.completed) ?? 0)) || startedAt;
74
73
  const id = managed?.id ?? `${session.id}:official:${nativeTurnId}`;
75
74
  const runId = managed?.runId ?? null;
76
- const projectedItems = runtimeAssistantProjectionMoreComplete(managed, items)
77
- ? managed.items
78
- : items;
75
+ const officialAssistantIds = new Set(assistants.flatMap(({ parts }) => parts.flatMap((part) => {
76
+ const value = record(part);
77
+ return value?.type === 'text' && typeof value.id === 'string'
78
+ ? [`${managed?.runId}:${boundedConversationItemId('opencode', value.id)}`]
79
+ : [];
80
+ })));
81
+ const pendingOfficialReply = managed?.items.some((item) => item.kind === 'assistant_message' && !officialAssistantIds.has(item.id));
82
+ const projectedItems = pendingOfficialReply ? managed.items : items;
79
83
  return {
80
84
  id,
81
85
  sessionId: session.id,
@@ -13,6 +13,35 @@ export function convergeConversationWatchPage(previous, previousObserved, next,
13
13
  const authority = isConversationHistorySource(previousAuthority)
14
14
  ? previousAuthority
15
15
  : previous?.source;
16
+ if (next.deferredOlderHistory === true && previous !== undefined)
17
+ return {
18
+ page: composeConversationRuntimePage(previous, next),
19
+ authority: authority ?? next.source
20
+ };
21
+ // A Runner history read can lag a Run that the Watch has already delivered. Keep the
22
+ // unconfirmed Runtime tail until the history includes that Turn; source priority alone
23
+ // does not establish that the returned window contains the latest conversation.
24
+ if (previous?.source === 'runtime' &&
25
+ previous.turns.length > 0 &&
26
+ next.source !== 'runtime' &&
27
+ (authority === undefined || sourceRank(next.source) >= sourceRank(authority))) {
28
+ const boundary = previous.turns.findLastIndex((turn) => next.turns.some((candidate) => sameConversationTurn(candidate, turn)));
29
+ if (boundary < 0)
30
+ return { page: previous, authority: next.source };
31
+ const tail = previous.turns
32
+ .slice(boundary + 1)
33
+ .filter((turn) => !next.turns.some((candidate) => sameConversationTurn(candidate, turn)));
34
+ if (tail.length > 0)
35
+ return {
36
+ page: {
37
+ ...next,
38
+ source: 'runtime',
39
+ snapshotSequence: previous.snapshotSequence,
40
+ turns: [...next.turns, ...tail]
41
+ },
42
+ authority: next.source
43
+ };
44
+ }
16
45
  if (previous === undefined ||
17
46
  authority === undefined ||
18
47
  sourceRank(next.source) >= sourceRank(authority))
@@ -91,13 +120,37 @@ export class ConversationHistoryService {
91
120
  const snapshotSequence = this.options.snapshotSequence();
92
121
  const readAt = Date.now();
93
122
  const activeCursor = decodeActiveHistoryCursor(input.cursor, session.id);
94
- if (activeCursor !== undefined || this.options.hasManagedActiveRun(session))
123
+ if (activeCursor !== undefined ||
124
+ this.options.hasManagedActiveRun(session) ||
125
+ this.hasManagedRuntimeHistory(session))
95
126
  return this.readManagedActive(session, input, activeCursor, snapshotSequence, readAt);
96
127
  return this.readHistory(session, input, snapshotSequence, readAt);
97
128
  }
98
129
  async readHistoryPage(session, input) {
130
+ if (this.hasManagedRuntimeHistory(session)) {
131
+ const runtimeTurns = this.options.runtime.listConversationTurns({
132
+ sessionId: session.id,
133
+ limit: 500
134
+ }).turns;
135
+ const olderCursor = this.options.hasManagedActiveRun(session)
136
+ ? {
137
+ sessionId: session.id,
138
+ stage: 'OFFICIAL',
139
+ beforeAt: oldestTurnActivityAt(runtimeTurns),
140
+ cursor: null
141
+ }
142
+ : undefined;
143
+ return this.readManagedActive(session, input, olderCursor, this.options.snapshotSequence(), Date.now());
144
+ }
99
145
  return this.readHistory(session, input, this.options.snapshotSequence(), Date.now());
100
146
  }
147
+ hasManagedRuntimeHistory(session) {
148
+ return (session.runner === 'mar-agent' &&
149
+ session.nativeControl === 'MAR_MANAGED' &&
150
+ this.options.runtime.getAgentSession(session.id) !== undefined &&
151
+ this.options.runtime.listConversationTurns({ sessionId: session.id, limit: 1 }).turns.length >
152
+ 0);
153
+ }
101
154
  async readHistory(session, input, snapshotSequence, readAt) {
102
155
  const selected = await this.readRunnerHistory(session, input);
103
156
  if (selected.page?.source === 'native')
@@ -113,9 +166,9 @@ export class ConversationHistoryService {
113
166
  ? []
114
167
  : this.options.runtime.listConversationTurns({ sessionId: session.id, limit: 500 }).turns;
115
168
  const initialOfficialTurns = official.turns;
116
- const lastRepresentedRuntimeIndex = runtimeTurns.findLastIndex((turn) => initialOfficialTurns.some((officialTurn) => sameHistoryConversationTurn(officialTurn, turn)));
169
+ const lastRepresentedRuntimeIndex = runtimeTurns.findLastIndex((turn) => initialOfficialTurns.some((officialTurn) => sameConversationTurn(officialTurn, turn)));
117
170
  const officialHasTurns = initialOfficialTurns.length > 0;
118
- const absentLocalTurns = runtimeTurns.filter((turn, index) => !initialOfficialTurns.some((officialTurn) => sameHistoryConversationTurn(officialTurn, turn)) &&
171
+ const absentLocalTurns = runtimeTurns.filter((turn, index) => !initialOfficialTurns.some((officialTurn) => sameConversationTurn(officialTurn, turn)) &&
119
172
  (!officialHasTurns || index > lastRepresentedRuntimeIndex));
120
173
  const missingLocalTurns = absentLocalTurns.slice(-limit);
121
174
  if (input.cursor === undefined &&
@@ -130,7 +183,7 @@ export class ConversationHistoryService {
130
183
  const runtimeTurnsByOfficialId = new Map();
131
184
  const runtimeSupplementalItemsByOfficialId = new Map();
132
185
  for (const officialTurn of officialTurns) {
133
- const runtimeTurn = runtimeTurns.find((candidate) => sameHistoryConversationTurn(officialTurn, candidate));
186
+ const runtimeTurn = runtimeTurns.find((candidate) => sameConversationTurn(officialTurn, candidate));
134
187
  if (runtimeTurn === undefined)
135
188
  continue;
136
189
  runtimeTurnsByOfficialId.set(officialTurn.id, runtimeTurn);
@@ -459,7 +512,7 @@ function latestTurnActivityAt(turn) {
459
512
  return Math.max(turn.startedAt ?? 0, turn.completedAt ?? 0);
460
513
  }
461
514
  function oldestTurnActivityAt(turns) {
462
- return turns.length === 0 ? 0 : Math.min(...turns.map(latestTurnActivityAt));
515
+ return turns.length === 0 ? 0 : Math.min(...turns.map(earliestTurnActivityAt));
463
516
  }
464
517
  function mergeRuntimeUserImageReferences(officialTurn, runtimeTurn, attachments) {
465
518
  const officialUser = officialTurn.items.find((item) => item.kind === 'user_message');
@@ -524,9 +577,6 @@ function mergeRuntimeUserImageReferences(officialTurn, runtimeTurn, attachments)
524
577
  : item)
525
578
  };
526
579
  }
527
- function sameHistoryConversationTurn(left, right) {
528
- return sameConversationTurn(left, right) || (left.runId !== null && left.runId === right.runId);
529
- }
530
580
  function sameConversationTurn(left, right) {
531
581
  if (left.id === right.id)
532
582
  return true;
@@ -6,7 +6,7 @@ function conversationSegmentPrefix(turn) {
6
6
  : undefined;
7
7
  const clientMessageId = payload?.clientMessageId;
8
8
  return typeof clientMessageId === 'string' && clientMessageId.length > 0
9
- ? `${turn.sessionId}:message:${clientMessageId}`
9
+ ? `message:${clientMessageId}`
10
10
  : turn.id;
11
11
  }
12
12
  export function conversationSegments(turn) {
@@ -115,6 +115,7 @@ export class NativeSessionWatchService {
115
115
  mode: this.options.managedActive(session) ? 'RUNNING' : 'HISTORY',
116
116
  expiresAt: Date.now() + TTL_MS,
117
117
  refresh: undefined,
118
+ refreshMode: undefined,
118
119
  sessionSignature: undefined,
119
120
  signature: undefined,
120
121
  page: undefined,
@@ -202,7 +203,7 @@ export class NativeSessionWatchService {
202
203
  get size() {
203
204
  return this.watches.size;
204
205
  }
205
- async refresh(sessionId, limit = 10, emit = true) {
206
+ async refresh(sessionId, limit = 10, mode = 'observation') {
206
207
  const watch = this.watches.get(sessionId);
207
208
  if (watch === undefined)
208
209
  return undefined;
@@ -212,20 +213,19 @@ export class NativeSessionWatchService {
212
213
  this.stop(sessionId);
213
214
  return undefined;
214
215
  }
215
- const refresh = this.performRefresh(sessionId, watch, limit, emit);
216
- watch.refresh = refresh;
217
- try {
218
- return await refresh;
219
- }
220
- finally {
216
+ const refresh = this.performRefresh(sessionId, watch, limit, mode).finally(() => {
221
217
  if (this.watches.get(sessionId) === watch) {
222
218
  watch.refresh = undefined;
219
+ watch.refreshMode = undefined;
223
220
  clearTimeout(watch.timer);
224
221
  watch.timer = this.refreshTimer(sessionId);
225
222
  }
226
- }
223
+ });
224
+ watch.refresh = refresh;
225
+ watch.refreshMode = mode;
226
+ return refresh;
227
227
  }
228
- async performRefresh(sessionId, watch, limit, emit) {
228
+ async performRefresh(sessionId, watch, limit, mode) {
229
229
  let stage = 'resolve';
230
230
  let session;
231
231
  const readHistoricalPage = watch.mode === 'HISTORY' && this.options.readHistoryPage !== undefined;
@@ -238,7 +238,7 @@ export class NativeSessionWatchService {
238
238
  return undefined;
239
239
  }
240
240
  watch.session = session;
241
- if (emit && this.options.managedActive(session))
241
+ if (mode === 'observation' && this.options.managedActive(session))
242
242
  return watch.page;
243
243
  stage = 'refresh_activity';
244
244
  await this.options.refreshActivity(session);
@@ -249,14 +249,26 @@ export class NativeSessionWatchService {
249
249
  const page = await (readHistoricalPage
250
250
  ? this.options.readHistoryPage(session, limit)
251
251
  : this.options.readPage(session, limit));
252
- if (emit && revisionBeforeRead !== watch.eventRevision)
253
- return watch.page;
252
+ if (revisionBeforeRead !== watch.eventRevision) {
253
+ // Initial still needs a full page for its new lease, whose pending events will
254
+ // replay above that page's sequence. Never publish this invalidated read to
255
+ // existing leases or replace their shared observation state.
256
+ if (mode !== 'initial')
257
+ return watch.page;
258
+ const initial = this.options.convergePage(watch.page, watch.observedPage, page, watch.authority).page;
259
+ if (watch.page === undefined && watch.signature === undefined)
260
+ watch.signature = JSON.stringify({
261
+ source: initial.source,
262
+ turns: initial.turns.slice(-(EMITTED_TURN_LIMIT + 1))
263
+ });
264
+ return initial;
265
+ }
254
266
  stage = 'present_session';
255
267
  const presented = this.options.present(session);
256
268
  const sessionSignature = JSON.stringify(presented);
257
269
  if (sessionSignature !== watch.sessionSignature) {
258
270
  watch.sessionSignature = sessionSignature;
259
- if (emit)
271
+ if (mode === 'observation')
260
272
  this.options.emitSession(presented);
261
273
  }
262
274
  watch.failures = 0;
@@ -265,7 +277,7 @@ export class NativeSessionWatchService {
265
277
  : page.turns.slice(-EMITTED_TURN_LIMIT);
266
278
  const observed = { ...page, turns };
267
279
  stage = 'converge_page';
268
- const convergence = this.options.convergePage(watch.page, watch.observedPage, observed, watch.authority);
280
+ const convergence = this.options.convergePage(watch.page, watch.observedPage, page, watch.authority);
269
281
  const converged = convergence.page;
270
282
  const accepted = converged.turns.length <= EMITTED_TURN_LIMIT + 1
271
283
  ? converged
@@ -285,14 +297,18 @@ export class NativeSessionWatchService {
285
297
  const changed = signature !== watch.signature;
286
298
  if (changed) {
287
299
  watch.signature = signature;
288
- watch.page = accepted;
289
300
  }
301
+ if (changed || watch.page === undefined)
302
+ watch.page = accepted;
290
303
  stage = 'emit_page';
291
- if (emit && completingConvergence)
304
+ if (completingConvergence && accepted.source === 'runtime')
305
+ this.releasePendingEvents(session.id);
306
+ else if (completingConvergence)
292
307
  this.emitSnapshotToLeases(session, accepted, true);
293
- else if (emit && changed)
308
+ else if (changed)
294
309
  this.emitObservedPage(session, accepted);
295
- return emit ? accepted : page;
310
+ // Initial uses the full result of the same authority decision as observation.
311
+ return mode === 'initial' ? converged : accepted;
296
312
  }
297
313
  catch (error) {
298
314
  watch.failures += 1;
@@ -345,15 +361,19 @@ export class NativeSessionWatchService {
345
361
  const activeAtStart = this.options.managedActive(session);
346
362
  if (watch !== undefined)
347
363
  watch.mode = activeAtStart ? 'RUNNING' : watch.mode;
348
- const cached = !activeAtStart && watch?.page !== undefined ? watch.page : undefined;
349
- let page = activeAtStart
350
- ? watch === undefined
351
- ? undefined
352
- : this.composeRuntimePage(watch.page, await this.readRuntimePage(watch, session))
353
- : (cached ??
354
- (watch === undefined
355
- ? undefined
356
- : await this.refresh(session.id, INITIAL_TURN_LIMIT, false)));
364
+ let page;
365
+ if (watch !== undefined) {
366
+ if (activeAtStart)
367
+ page = this.composeRuntimePage(watch.page, await this.readRuntimePage(watch, session));
368
+ else {
369
+ // An initial subscription must read after any older observation, not join its
370
+ // potentially smaller or already-stale page.
371
+ if (watch.refresh !== undefined &&
372
+ (watch.page !== undefined || watch.refreshMode === 'observation'))
373
+ await watch.refresh;
374
+ page = await this.refresh(session.id, INITIAL_TURN_LIMIT, 'initial');
375
+ }
376
+ }
357
377
  if (page === undefined)
358
378
  throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
359
379
  if (lease.snapshotSent)
@@ -506,7 +526,7 @@ export class NativeSessionWatchService {
506
526
  watch.mode !== 'CONVERGING' ||
507
527
  watch.transitionGeneration !== generation)
508
528
  return;
509
- await this.refresh(session.id, INITIAL_TURN_LIMIT, true);
529
+ await this.refresh(session.id, INITIAL_TURN_LIMIT);
510
530
  }
511
531
  emitLeaseEvent(lease, event) {
512
532
  this.options.emitWatchEvent?.({
@@ -146,7 +146,12 @@ export class RunEventService {
146
146
  }
147
147
  if (!terminalExecution)
148
148
  return;
149
- if (targetsCurrentTurn && createdTurn === undefined) {
149
+ // A completed reply has already been delivered through its own stream event. Only a
150
+ // continuing Run, an exceptional termination, or a pending item needs a final Turn update.
151
+ if (targetsCurrentTurn &&
152
+ createdTurn === undefined &&
153
+ (finalStatus !== 'SUCCEEDED' ||
154
+ targetTurn?.items.some((item) => item.status === 'PENDING' || item.status === 'IN_PROGRESS'))) {
150
155
  const turn = this.options.runtime.conversationTurnForRun(runId);
151
156
  if (turn !== undefined)
152
157
  this.options.emitWorkbench('conversation', { turn });
@@ -39,15 +39,19 @@ export class SessionCommandService {
39
39
  (await this.options.projection.resolveCurrent(input.sessionId));
40
40
  if (resolved === undefined)
41
41
  throw new Error('SESSION_NOT_FOUND');
42
- if (resolved.nativeControl === 'EXTERNAL')
43
- throw new Error('SESSION_ACTIVATION_REQUIRED');
44
- const session = resolved;
45
42
  const command = this.options
46
43
  .profiles()
47
- .find((profile) => profile.runner === session.runner)
44
+ .find((profile) => profile.runner === resolved.runner)
48
45
  ?.commands.find((candidate) => isPlainRecord(candidate) && candidate.id === input.commandId);
46
+ if (resolved.nativeControl === 'EXTERNAL' && command?.source !== 'MAR_RUNTIME')
47
+ throw new Error('SESSION_ACTIVATION_REQUIRED');
49
48
  if (command?.available !== true)
50
49
  throw new Error('RUNNER_COMMAND_UNAVAILABLE');
50
+ const session = resolved.nativeControl === 'EXTERNAL' && command.source === 'MAR_RUNTIME'
51
+ ? await this.options.resolveForCommand(input.sessionId)
52
+ : resolved;
53
+ if (session.nativeControl === 'EXTERNAL')
54
+ throw new Error('SESSION_ACTIVATION_REQUIRED');
51
55
  if ((command.interaction === 'IMMEDIATE_ACTION' ||
52
56
  command.interaction === 'DESTRUCTIVE_ACTION') &&
53
57
  this.options.runtime.hasActiveSessionLease(session.id))
@@ -159,7 +159,9 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
159
159
  const runtimeErrors = (runtimeTurn?.items ?? [])
160
160
  .filter((item) => item.kind === 'error')
161
161
  .map((item) => ({ ...item, turnId, runId: runtimeTurn?.runId ?? null }));
162
- const items = runtimeAssistantProjectionMoreComplete(runtimeTurn, nativeItems)
162
+ // Native transcripts do not expose a terminal event for an individual response.
163
+ // Once linked to a managed Run, the Runtime owns that Turn until it leaves memory.
164
+ const items = runtimeTurn !== undefined
163
165
  ? runtimeTurn.items.map((item) => ({
164
166
  ...item,
165
167
  turnId,
@@ -225,22 +227,6 @@ export function nativeConversationPage(session, history, input, runtimeTurns = [
225
227
  : precedingWindow
226
228
  };
227
229
  }
228
- export function runtimeAssistantProjectionMoreComplete(runtimeTurn, projectedItems) {
229
- if (runtimeTurn === undefined)
230
- return false;
231
- const assistantStats = (items) => items.reduce((stats, item) => {
232
- if (item.kind !== 'assistant_message' || !isPlainRecord(item.payload))
233
- return stats;
234
- return {
235
- count: stats.count + 1,
236
- characters: stats.characters +
237
- (typeof item.payload.text === 'string' ? item.payload.text.length : 0)
238
- };
239
- }, { count: 0, characters: 0 });
240
- const runtime = assistantStats(runtimeTurn.items);
241
- const projected = assistantStats(projectedItems);
242
- return runtime.count > projected.count || runtime.characters > projected.characters;
243
- }
244
230
  function runtimeTurnClientMessageId(turn) {
245
231
  const user = turn.items.find((item) => item.kind === 'user_message');
246
232
  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.105",
3
+ "version": "0.9.107",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -30,9 +30,9 @@
30
30
  "npm": "11.12.1",
31
31
  "ws": "^8.21.3",
32
32
  "zod": "4.4.3",
33
- "@myagentroam/agent": "0.9.105",
34
- "@myagentroam/code-navigation": "0.9.105",
35
- "@myagentroam/protocol": "0.9.105"
33
+ "@myagentroam/agent": "0.9.107",
34
+ "@myagentroam/code-navigation": "0.9.107",
35
+ "@myagentroam/protocol": "0.9.107"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/ws": "^8.18.1"