@myagentroam/node 0.11.2 → 0.11.4

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.
@@ -7,6 +7,8 @@ import { nodeLog } from './operational.js';
7
7
  import { stripClaudePlanTag } from './runner-command-engine.js';
8
8
  import { stripGoalStartPrompt } from './service/goal-runtime.js';
9
9
  import { parseCodexComposerImageMarkers } from './runner/codex/composer-image-markers.js';
10
+ import { codexTranscriptSourceItemId } from './runner/codex/conversation-parser.js';
11
+ import { claudeAssistantBlocks, extractClaudeThinkingText } from './runner/claude/conversation-parser.js';
10
12
  import { extractClaudeToolResults } from './runner/claude/tool-normalizer.js';
11
13
  const DISCOVERY_READ_BYTES = 32 * 1024;
12
14
  const TRANSCRIPT_HEADER_READ_BYTES = 32 * 1024;
@@ -477,9 +479,11 @@ async function readTranscript(path, runner, byteLimit, page, targetLines = Numbe
477
479
  const generatedImage = codexGeneratedImageEvent(value);
478
480
  if (generatedImage !== undefined)
479
481
  items.push(generatedImage);
482
+ const sourceItemId = runner === 'codex' ? codexTranscriptSourceItemId(value, codexTurnId) : undefined;
480
483
  for (const parsedItem of transcriptItems(runner, value)) {
481
484
  const item = {
482
485
  ...parsedItem,
486
+ ...(sourceItemId === undefined ? {} : { sourceItemId }),
483
487
  ...(recordId === undefined ? {} : { nativeId: recordId }),
484
488
  ...(runner === 'codex' &&
485
489
  parsedItem.kind === 'message' &&
@@ -615,6 +619,32 @@ function positiveInteger(value) {
615
619
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
616
620
  }
617
621
  function transcriptItems(runner, record) {
622
+ if (runner === 'claude-code' && record.type === 'assistant') {
623
+ return claudeAssistantBlocks(record).flatMap(({ record: blockRecord, blockIndex, sourceItemId }) => {
624
+ const block = contentBlocks(blockRecord)[0];
625
+ if (block?.type === 'thinking') {
626
+ const text = extractClaudeThinkingText(blockRecord)[0] ?? '';
627
+ return text.length === 0
628
+ ? []
629
+ : [
630
+ {
631
+ kind: 'reasoning_summary',
632
+ representation: 'visible_thinking',
633
+ sections: [{ index: blockIndex, text }],
634
+ createdAt: timestampOf(record),
635
+ ...(sourceItemId === undefined ? {} : { sourceItemId })
636
+ }
637
+ ];
638
+ }
639
+ return transcriptRecordItems(runner, blockRecord).map((item) => ({
640
+ ...item,
641
+ ...(sourceItemId === undefined ? {} : { sourceItemId })
642
+ }));
643
+ });
644
+ }
645
+ return transcriptRecordItems(runner, record);
646
+ }
647
+ function transcriptRecordItems(runner, record) {
618
648
  const createdAt = timestampOf(record);
619
649
  const results = [...toolResultsOf(record, createdAt), ...codexToolResultsOf(record, createdAt)];
620
650
  if (results.length > 0)
@@ -689,7 +719,7 @@ function codexReasoningSummariesOf(record, createdAt) {
689
719
  return text.length === 0 ? [] : [{ index, text }];
690
720
  })
691
721
  : [];
692
- return [{ kind: 'reasoning_summary', sections, createdAt }];
722
+ return [{ kind: 'reasoning_summary', representation: 'summary', sections, createdAt }];
693
723
  }
694
724
  function unsupportedTranscriptItems(record, createdAt) {
695
725
  return contentBlocks(record).flatMap((block) => {
@@ -0,0 +1,42 @@
1
+ import { boundedConversationItemId, isPlainRecord, claudeContentBlocks, compactRunnerText } from '../../util/node-operation-parsers.js';
2
+ export function extractClaudeThinkingText(record) {
3
+ return claudeContentBlocks(record).flatMap((block) => {
4
+ // Completed public blocks only; signatures and redacted/encrypted data are never read.
5
+ if (block.type !== 'thinking' || typeof block.thinking !== 'string')
6
+ return [];
7
+ const text = compactRunnerText(block.thinking.trim(), 10_000);
8
+ return text.length === 0 ? [] : [text];
9
+ });
10
+ }
11
+ /** Preserve native block order and identity across SDK callbacks and transcript reads. */
12
+ export function claudeAssistantBlocks(record) {
13
+ const message = isPlainRecord(record.message) ? record.message : undefined;
14
+ if (message === undefined)
15
+ return [];
16
+ const content = typeof message.content === 'string'
17
+ ? [{ type: 'text', text: message.content }]
18
+ : Array.isArray(message.content)
19
+ ? message.content
20
+ : [];
21
+ const messageId = typeof record.uuid === 'string'
22
+ ? record.uuid
23
+ : typeof message.id === 'string'
24
+ ? message.id
25
+ : undefined;
26
+ return content.flatMap((block, blockIndex) => {
27
+ if (!isPlainRecord(block))
28
+ return [];
29
+ const sourceItemId = block.type === 'tool_use' && typeof block.id === 'string'
30
+ ? boundedConversationItemId('claude-tool', block.id)
31
+ : messageId === undefined
32
+ ? undefined
33
+ : JSON.stringify(['claude', messageId, blockIndex]);
34
+ return [
35
+ {
36
+ record: { ...record, message: { ...message, content: [block] } },
37
+ blockIndex,
38
+ ...(sourceItemId === undefined ? {} : { sourceItemId })
39
+ }
40
+ ];
41
+ });
42
+ }
@@ -1,8 +1,9 @@
1
+ import { claudeAssistantBlocks, extractClaudeThinkingText } from './conversation-parser.js';
1
2
  import { ClaudeUsageNormalizer } from './tool-normalizer.js';
2
3
  import { createEnvelope } from '@myagentroam/protocol';
3
4
  import { ClaudeChannelGate } from '../../claude-channel.js';
4
5
  import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
5
- import { boundedConversationItemId, claudePlanText, compactRunnerText, extractClaudeText, extractClaudeThinkingSummaries, extractClaudeToolUses } from '../codex/conversation-parser.js';
6
+ import { boundedConversationItemId, claudePlanText, compactRunnerText, extractClaudeText, extractClaudeToolUses } from '../codex/conversation-parser.js';
6
7
  import { normalizeClaudeTool, extractClaudeToolResults } from './tool-normalizer.js';
7
8
  import { claudeContentBlocks } from '../../util/node-operation-parsers.js';
8
9
  import { parseSecretEnvironment } from '../../util/secret-environment.js';
@@ -344,67 +345,84 @@ export class ClaudeManagedRunController {
344
345
  finalOutputTokens.set(snapshotId, snapshotUsage.output_tokens);
345
346
  settleGeneration(snapshotId, receiptAt);
346
347
  }
347
- const text = extractClaudeText(record);
348
- if (mainTask && (text.length > 0 || extractClaudeThinkingSummaries(record).length > 0))
349
- observeResponse(typeof record.message?.id === 'string'
350
- ? record.message.id
351
- : undefined);
352
- const planText = claudePlanText(text);
353
- if (planText !== undefined)
354
- this.host.emitItem(runId, {
355
- itemId: boundedConversationItemId('claude-plan', typeof record.uuid === 'string' ? record.uuid : `${runId}:plan`),
356
- kind: 'plan',
357
- status: 'COMPLETED',
358
- payload: { explanation: compactRunnerText(planText, 20_000), steps: [] }
359
- });
360
- else if (text.length > 0)
361
- this.host.emit(runId, 'text.delta', { text });
362
- for (const [index, text] of extractClaudeThinkingSummaries(record).entries()) {
363
- this.host.emitItem(runId, {
364
- itemId: boundedConversationItemId('claude-reasoning', `${typeof record.uuid === 'string' ? record.uuid : runId}:${index}`),
365
- kind: 'reasoning_summary',
366
- status: 'COMPLETED',
367
- payload: { sections: [{ index, text }] }
368
- });
369
- }
370
- for (const tool of extractClaudeToolUses(record)) {
371
- const latencyMs = mainTask ? responseLatency.toolStarted(tool.id) : undefined;
372
- if (latencyMs !== undefined)
373
- this.host.emit(runId, 'run.performance', { latencyMs });
374
- if (tool.name === 'AskUserQuestion') {
375
- toolKinds.set(tool.id, 'user_input_request');
376
- continue;
348
+ const fallbackMessageId = crypto.randomUUID();
349
+ for (const { record: blockRecord, blockIndex, sourceItemId } of claudeAssistantBlocks(record)) {
350
+ const text = extractClaudeText(blockRecord);
351
+ if (mainTask &&
352
+ (text.length > 0 || extractClaudeThinkingText(blockRecord).length > 0))
353
+ observeResponse(typeof record.message?.id === 'string'
354
+ ? record.message.id
355
+ : undefined);
356
+ const planText = claudePlanText(text);
357
+ if (planText !== undefined)
358
+ this.host.emitItem(runId, {
359
+ itemId: boundedConversationItemId('claude-plan', sourceItemId ?? `${fallbackMessageId}:${blockIndex}`),
360
+ ...(sourceItemId === undefined ? {} : { sourceItemId }),
361
+ kind: 'plan',
362
+ status: 'COMPLETED',
363
+ payload: { explanation: compactRunnerText(planText, 20_000), steps: [] }
364
+ });
365
+ else if (text.length > 0)
366
+ this.host.emitItem(runId, {
367
+ itemId: boundedConversationItemId('claude-text', sourceItemId ?? `${fallbackMessageId}:${blockIndex}`),
368
+ ...(sourceItemId === undefined ? {} : { sourceItemId }),
369
+ kind: 'assistant_message',
370
+ status: 'COMPLETED',
371
+ payload: { text, format: 'markdown', model: null }
372
+ });
373
+ for (const text of extractClaudeThinkingText(blockRecord)) {
374
+ this.host.emitItem(runId, {
375
+ itemId: boundedConversationItemId('claude-reasoning', sourceItemId ?? `${fallbackMessageId}:${blockIndex}`),
376
+ ...(sourceItemId === undefined ? {} : { sourceItemId }),
377
+ kind: 'reasoning',
378
+ status: 'COMPLETED',
379
+ payload: {
380
+ representation: 'visible_thinking',
381
+ sections: [{ index: blockIndex, text }]
382
+ }
383
+ });
377
384
  }
378
- const itemId = boundedConversationItemId('claude-tool', tool.id);
379
- toolInputs.set(tool.id, tool);
380
- const normalized = normalizeClaudeTool(tool.name, tool.input, null, false, tool.inputTruncated, knownSubagents);
381
- const kind = normalized.kind;
382
- toolKinds.set(tool.id, kind);
383
- this.host.emitItem(runId, {
384
- itemId,
385
- kind,
386
- status: 'IN_PROGRESS',
387
- payload: normalized.payload
388
- });
389
- }
390
- for (const [index, block] of claudeContentBlocks(record).entries()) {
391
- const type = typeof block.type === 'string' ? block.type : 'unknown';
392
- if (type === 'text' ||
393
- type === 'thinking' ||
394
- type === 'redacted_thinking' ||
395
- type === 'encrypted_thinking' ||
396
- type === 'tool_use' ||
397
- type === 'tool_result')
398
- continue;
399
- this.host.emitItem(runId, {
400
- itemId: boundedConversationItemId('claude-item', `${typeof record.uuid === 'string' ? record.uuid : runId}:${index}`),
401
- kind: 'unknown',
402
- status: 'COMPLETED',
403
- payload: {
404
- sourceEventType: `claude-code:${type}`,
405
- label: `Claude Code ${compactRunnerText(type, 128)}`
385
+ for (const tool of extractClaudeToolUses(blockRecord)) {
386
+ const latencyMs = mainTask ? responseLatency.toolStarted(tool.id) : undefined;
387
+ if (latencyMs !== undefined)
388
+ this.host.emit(runId, 'run.performance', { latencyMs });
389
+ if (tool.name === 'AskUserQuestion') {
390
+ toolKinds.set(tool.id, 'user_input_request');
391
+ continue;
406
392
  }
407
- });
393
+ const itemId = boundedConversationItemId('claude-tool', tool.id);
394
+ toolInputs.set(tool.id, tool);
395
+ const normalized = normalizeClaudeTool(tool.name, tool.input, null, false, tool.inputTruncated, knownSubagents);
396
+ const kind = normalized.kind;
397
+ toolKinds.set(tool.id, kind);
398
+ this.host.emitItem(runId, {
399
+ itemId,
400
+ sourceItemId: itemId,
401
+ kind,
402
+ status: 'IN_PROGRESS',
403
+ payload: normalized.payload
404
+ });
405
+ }
406
+ for (const block of claudeContentBlocks(blockRecord)) {
407
+ const type = typeof block.type === 'string' ? block.type : 'unknown';
408
+ if (type === 'text' ||
409
+ type === 'thinking' ||
410
+ type === 'redacted_thinking' ||
411
+ type === 'encrypted_thinking' ||
412
+ type === 'tool_use' ||
413
+ type === 'tool_result')
414
+ continue;
415
+ this.host.emitItem(runId, {
416
+ itemId: boundedConversationItemId('claude-item', sourceItemId ?? `${fallbackMessageId}:${blockIndex}`),
417
+ ...(sourceItemId === undefined ? {} : { sourceItemId }),
418
+ kind: 'unknown',
419
+ status: 'COMPLETED',
420
+ payload: {
421
+ sourceEventType: `claude-code:${type}`,
422
+ label: `Claude Code ${compactRunnerText(type, 128)}`
423
+ }
424
+ });
425
+ }
408
426
  }
409
427
  }
410
428
  if (record.type === 'tool_progress' && typeof record.tool_use_id === 'string') {
@@ -135,6 +135,16 @@ export function normalizeClaudeTool(toolName, input, output, failed, truncated,
135
135
  progressLabel: null,
136
136
  errorCode: failed ? 'TOOL_EXECUTION_FAILED' : null,
137
137
  truncated: outputTruncated,
138
+ ...(toolName === 'Read'
139
+ ? {
140
+ toolPresentation: {
141
+ kind: 'READ',
142
+ paths: typeof values?.file_path === 'string' && values.file_path.length > 0
143
+ ? [compactRunnerText(values.file_path, 4096)]
144
+ : []
145
+ }
146
+ }
147
+ : {}),
138
148
  ...subagent
139
149
  }
140
150
  };
@@ -629,6 +629,20 @@ export function codexPlanText(value) {
629
629
  * A final Claude tagged Plan Mode plan is fully wrapped in `<proposed_plan>` tags;
630
630
  * text with missing or unclosed tags remains a regular assistant message.
631
631
  */
632
+ export function codexSourceItemId(nativeTurnId, itemId) {
633
+ return JSON.stringify([nativeTurnId, itemId]);
634
+ }
635
+ export function codexTranscriptSourceItemId(record, nativeTurnId) {
636
+ if (nativeTurnId === undefined ||
637
+ record.type !== 'response_item' ||
638
+ !isPlainRecord(record.payload))
639
+ return undefined;
640
+ const payload = record.payload;
641
+ const id = typeof payload.call_id === 'string' ? payload.call_id : payload.id;
642
+ return typeof id === 'string'
643
+ ? codexSourceItemId(nativeTurnId, boundedConversationItemId('codex', id))
644
+ : undefined;
645
+ }
632
646
  export function codexOfficialItem(session, nativeTurnId, value, turnSequence, itemSequence, startedAt, completedAt) {
633
647
  if (!isPlainRecord(value) || typeof value.type !== 'string' || value.type === 'subAgentActivity')
634
648
  return undefined;
@@ -645,6 +659,7 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
645
659
  : `${nativeTurnId}:${itemSequence}`;
646
660
  const base = {
647
661
  id: `${session.id}:official:${nativeTurnId}:${id}`,
662
+ sourceItemId: codexSourceItemId(nativeTurnId, boundedConversationItemId('codex', id)),
648
663
  sessionId: session.id,
649
664
  turnId: `${session.id}:official:${nativeTurnId}`,
650
665
  runId: null,
@@ -689,7 +704,7 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
689
704
  ? [{ index, text: compactRunnerText(text, 10_000) }]
690
705
  : [])
691
706
  : [];
692
- return { ...base, kind: 'reasoning_summary', payload: { sections } };
707
+ return { ...base, kind: 'reasoning', payload: { representation: 'summary', sections } };
693
708
  }
694
709
  if (value.type === 'commandExecution') {
695
710
  const output = typeof value.aggregatedOutput === 'string' ? value.aggregatedOutput : '';
@@ -835,9 +850,9 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
835
850
  : [];
836
851
  return {
837
852
  itemId,
838
- kind: 'reasoning_summary',
853
+ kind: 'reasoning',
839
854
  status: completed ? 'COMPLETED' : 'IN_PROGRESS',
840
- payload: { sections }
855
+ payload: { representation: 'summary', sections }
841
856
  };
842
857
  }
843
858
  const planText = codexPlanText(value);
@@ -6,7 +6,7 @@ import { safeErrorCode } from '../../util/safe-error.js';
6
6
  import { runnerErrorProjection } from '../../util/runner-error.js';
7
7
  import { ResponseLatencyTracker } from '../../service/response-latency-tracker.js';
8
8
  import { GenerationTimingTracker } from '../../service/generation-timing-tracker.js';
9
- import { boundedConversationItemId, codexImageAttachments, codexLiveConversationItem, CodexUsageNormalizer, codexUserInputQuestions, compactRunnerText, isPlainRecord } from './conversation-parser.js';
9
+ import { boundedConversationItemId, codexImageAttachments, codexLiveConversationItem, codexSourceItemId, CodexUsageNormalizer, codexUserInputQuestions, compactRunnerText, isPlainRecord } from './conversation-parser.js';
10
10
  export function resolveCodexNotificationRun(runs, input) {
11
11
  const entries = [...runs.entries()];
12
12
  if (input.turnId !== undefined) {
@@ -35,6 +35,15 @@ export class CodexManagedRunController {
35
35
  this.runner = runner;
36
36
  this.host = host;
37
37
  }
38
+ emitItem(runId, item) {
39
+ const nativeTurnId = this.runner.execution.runs.get(runId)?.turnId;
40
+ this.host.emitItem(runId, nativeTurnId === undefined
41
+ ? item
42
+ : {
43
+ ...item,
44
+ sourceItemId: codexSourceItemId(nativeTurnId, item.itemId)
45
+ });
46
+ }
38
47
  async executeCommand(session, command, input, context) {
39
48
  if (command.id === 'plan') {
40
49
  const environmentLeaseId = crypto.randomUUID();
@@ -222,6 +231,9 @@ export class CodexManagedRunController {
222
231
  : undefined;
223
232
  this.host.emit(runId, 'text.delta', {
224
233
  text: params.delta,
234
+ ...(itemId === undefined || turnId === undefined
235
+ ? {}
236
+ : { sourceItemId: codexSourceItemId(turnId, itemId) }),
225
237
  ...(itemId === undefined ? {} : { itemId })
226
238
  });
227
239
  if (params.delta.length > 0)
@@ -245,11 +257,14 @@ export class CodexManagedRunController {
245
257
  const index = typeof params.summaryIndex === 'number' && Number.isInteger(params.summaryIndex)
246
258
  ? params.summaryIndex
247
259
  : 0;
248
- this.host.emitItem(runId, {
260
+ this.emitItem(runId, {
249
261
  itemId: boundedConversationItemId('codex', params.itemId),
250
- kind: 'reasoning_summary',
262
+ kind: 'reasoning',
251
263
  status: 'IN_PROGRESS',
252
- payload: { sections: [{ index, text: compactRunnerText(params.delta, 10_000) }] },
264
+ payload: {
265
+ representation: 'summary',
266
+ sections: [{ index, text: compactRunnerText(params.delta, 10_000) }]
267
+ },
253
268
  merge: true,
254
269
  append: 'sections'
255
270
  });
@@ -262,7 +277,7 @@ export class CodexManagedRunController {
262
277
  if (notification.method === 'item/commandExecution/outputDelta' &&
263
278
  typeof params.itemId === 'string' &&
264
279
  typeof params.delta === 'string') {
265
- this.host.emitItem(runId, {
280
+ this.emitItem(runId, {
266
281
  itemId: boundedConversationItemId('codex', params.itemId),
267
282
  kind: 'command_execution',
268
283
  status: 'IN_PROGRESS',
@@ -275,7 +290,7 @@ export class CodexManagedRunController {
275
290
  if (notification.method === 'item/mcpToolCall/progress' &&
276
291
  typeof params.itemId === 'string' &&
277
292
  typeof params.message === 'string') {
278
- this.host.emitItem(runId, {
293
+ this.emitItem(runId, {
279
294
  itemId: boundedConversationItemId('codex', params.itemId),
280
295
  kind: 'tool_call',
281
296
  status: 'IN_PROGRESS',
@@ -289,7 +304,7 @@ export class CodexManagedRunController {
289
304
  const attachments = this.host.appendImages(runId, codexImageAttachments(params.item));
290
305
  const item = codexLiveConversationItem(params.item, notification.method === 'item/completed', attachments);
291
306
  if (item !== undefined) {
292
- this.host.emitItem(runId, item);
307
+ this.emitItem(runId, item);
293
308
  if (item.kind === 'context_compaction') {
294
309
  activeRun.generationCompacting = notification.method === 'item/started';
295
310
  if (activeRun.generationCompacting) {
@@ -317,7 +332,7 @@ export class CodexManagedRunController {
317
332
  this.host.emit(runId, 'run.performance', { latencyMs });
318
333
  }
319
334
  }
320
- else if (['assistant_message', 'reasoning_summary', 'plan'].includes(item.kind)) {
335
+ else if (['assistant_message', 'reasoning', 'plan'].includes(item.kind)) {
321
336
  // A started reasoning item is itself a semantic response, even before its summary.
322
337
  if (notification.method === 'item/started')
323
338
  this.beginGeneration(runId, receiptAt);
@@ -527,7 +542,7 @@ export class CodexManagedRunController {
527
542
  allowOther: question.allowOther
528
543
  }))
529
544
  });
530
- this.host.emitItem(runId, {
545
+ this.emitItem(runId, {
531
546
  itemId: boundedConversationItemId('codex-input', requestId),
532
547
  kind: 'user_input_request',
533
548
  status: 'PENDING',
@@ -722,7 +737,7 @@ export class CodexManagedRunController {
722
737
  }
723
738
  emitError(runId, error, fallbackCode) {
724
739
  const projected = runnerErrorProjection(error, fallbackCode);
725
- this.host.emitItem(runId, {
740
+ this.emitItem(runId, {
726
741
  itemId: boundedConversationItemId('runner-error', runId),
727
742
  kind: 'error',
728
743
  status: 'FAILED',
@@ -52,11 +52,11 @@ export class MarAgentConversationNormalizer {
52
52
  }
53
53
  ];
54
54
  }
55
- if (event.type === 'message.completed') {
55
+ if (event.type === 'message.started' || event.type === 'message.completed') {
56
56
  const modelName = record(event)?.modelName;
57
57
  const itemId = this.messageItemId(event.itemId);
58
58
  const payload = {
59
- text: event.text,
59
+ text: event.type === 'message.completed' ? event.text : '',
60
60
  format: 'markdown',
61
61
  model: typeof modelName === 'string' ? modelName : null
62
62
  };
@@ -64,7 +64,7 @@ export class MarAgentConversationNormalizer {
64
64
  {
65
65
  itemId,
66
66
  kind: 'assistant_message',
67
- status: 'COMPLETED',
67
+ status: event.type === 'message.completed' ? 'COMPLETED' : 'IN_PROGRESS',
68
68
  payload
69
69
  }
70
70
  ];
@@ -78,20 +78,24 @@ export class MarAgentConversationNormalizer {
78
78
  payload: { explanation: event.text, steps: [] }
79
79
  }
80
80
  ];
81
- if (event.type === 'reasoning.summary')
81
+ if (event.type === 'reasoning.completed')
82
82
  return [
83
83
  {
84
- itemId: itemId('reasoning', event.eventId),
85
- kind: 'reasoning_summary',
84
+ itemId: itemId('reasoning', event.itemId),
85
+ kind: 'reasoning',
86
86
  status: 'COMPLETED',
87
- payload: { sections: [{ index: 0, text: compactRunnerText(event.text, 10_000) }] }
87
+ payload: {
88
+ representation: event.representation,
89
+ sections: [{ index: 0, text: compactRunnerText(event.text, 10_000) }]
90
+ }
88
91
  }
89
92
  ];
90
93
  if (event.type === 'tool.started' ||
91
94
  event.type === 'tool.completed' ||
92
95
  event.type === 'tool.failed') {
93
- // Native web calls and generated artifacts stay visible independently of cell observations.
96
+ // Read/web calls and generated artifacts stay visible independently of cell observations.
94
97
  if (event.parentCallId !== undefined &&
98
+ event.toolName !== 'read' &&
95
99
  event.toolName !== 'web_search' &&
96
100
  event.toolName !== 'web_fetch' &&
97
101
  !isImageGenerationTool(event.toolName))
@@ -352,7 +356,7 @@ export class MarAgentConversationNormalizer {
352
356
  if (observation.id !== undefined)
353
357
  this.#codeModeObservationIds.add(observation.id);
354
358
  this.#codeModeSourceCallIds.add(observation.source.callId);
355
- items.push(...projected);
359
+ items.push(...projected.map((item) => 'callId' in event ? { ...item, parentItemId: itemId('tool', event.callId) } : item));
356
360
  }
357
361
  return items;
358
362
  }
@@ -665,6 +669,16 @@ function genericToolItem(id, toolName, status, input, outputSummary = null, erro
665
669
  };
666
670
  }
667
671
  function projectToolPresentation(toolName, input, output, startedAt) {
672
+ if (toolName === 'read') {
673
+ const reads = record(input)?.reads;
674
+ const paths = (Array.isArray(reads) ? reads : [])
675
+ .flatMap((entry) => {
676
+ const path = record(entry)?.path;
677
+ return typeof path === 'string' && path.length > 0 ? [compactRunnerText(path, 4096)] : [];
678
+ })
679
+ .slice(0, 16);
680
+ return { kind: 'READ', paths };
681
+ }
668
682
  const category = toolName === 'web_search'
669
683
  ? 'WEB_SEARCH'
670
684
  : toolName === 'web_fetch'
@@ -102,7 +102,7 @@ export function projectMarAgentHistory(session, entries, options = {}) {
102
102
  const attachments = projected.generatedImages === undefined
103
103
  ? []
104
104
  : (options.registerGeneratedImages?.(event.sessionId, projected.generatedImages) ?? []);
105
- replace(projectionTurn, historyItem(session, projectionTurn, projected.itemId, projected.kind, projected.status, attachments.length === 0 ? projected.payload : { ...projected.payload, attachments }, at, projected.permissionStop));
105
+ replace(projectionTurn, historyItem(session, projectionTurn, projected.itemId, projected.kind, projected.status, attachments.length === 0 ? projected.payload : { ...projected.payload, attachments }, at, projected.permissionStop, projected.parentItemId));
106
106
  }
107
107
  }
108
108
  return [...turns.values()];
@@ -129,13 +129,13 @@ function record(value) {
129
129
  ? value
130
130
  : undefined;
131
131
  }
132
- function historyItem(session, turn, id, kind, status, payload, at, permissionStop) {
132
+ function historyItem(session, turn, id, kind, status, payload, at, permissionStop, parentItemId) {
133
133
  return {
134
134
  id,
135
135
  sessionId: session.id,
136
136
  turnId: turn.id,
137
137
  runId: turn.runId,
138
- parentItemId: null,
138
+ parentItemId: parentItemId ?? null,
139
139
  sourceSequence: null,
140
140
  revision: 0,
141
141
  kind,
@@ -709,6 +709,7 @@ export class MarAgentManagedRunController {
709
709
  []);
710
710
  this.host.emitItem(runId, {
711
711
  itemId: item.itemId,
712
+ ...(item.parentItemId === undefined ? {} : { parentItemId: item.parentItemId }),
712
713
  kind: item.kind,
713
714
  status: item.status,
714
715
  payload: attachments.length === 0 ? item.payload : { ...item.payload, attachments },
@@ -139,7 +139,12 @@ function openCodeTurn(session, user, userParts, assistants, sequence, managed) {
139
139
  for (const part of assistant.parts) {
140
140
  const projected = partItem(session, nativeTurnId, part, assistant.info.mode === 'plan', startedAt, completedAt, sequence);
141
141
  if (projected !== undefined)
142
- items.push(projected);
142
+ items.push({
143
+ ...projected,
144
+ ...(typeof assistant.info.id === 'string' && typeof record(part)?.id === 'string'
145
+ ? { sourceItemId: openCodeSourceItemId(assistant.info.id, record(part).id) }
146
+ : {})
147
+ });
143
148
  }
144
149
  }
145
150
  if (items.length === 0)
@@ -173,12 +178,17 @@ function partItem(session, turnId, raw, planMode, startedAt, completedAt, sequen
173
178
  const part = record(raw);
174
179
  if (typeof part?.id !== 'string' || typeof part.type !== 'string')
175
180
  return undefined;
181
+ if (part.type === 'reasoning' && (typeof part.text !== 'string' || !part.text.trim()))
182
+ return undefined;
176
183
  if ((part.type === 'text' || part.type === 'reasoning') && typeof part.text === 'string')
177
- return item(session, turnId, part.id, part.type === 'text' ? (planMode ? 'plan' : 'assistant_message') : 'reasoning_summary', completedAt === null ? 'IN_PROGRESS' : 'COMPLETED', part.type === 'text'
184
+ return item(session, turnId, part.id, part.type === 'text' ? (planMode ? 'plan' : 'assistant_message') : 'reasoning', completedAt === null ? 'IN_PROGRESS' : 'COMPLETED', part.type === 'text'
178
185
  ? planMode
179
186
  ? { explanation: compactRunnerText(part.text, 20_000), steps: [] }
180
187
  : { text: compactRunnerText(part.text, 20_000), format: 'markdown', model: null }
181
- : { sections: [{ index: 0, text: compactRunnerText(part.text, 10_000) }] }, startedAt, completedAt, sequence);
188
+ : {
189
+ representation: 'unspecified',
190
+ sections: [{ index: 0, text: compactRunnerText(part.text, 10_000) }]
191
+ }, startedAt, completedAt, sequence);
182
192
  if (part.type === 'tool' && typeof part.tool === 'string') {
183
193
  const state = record(part.state);
184
194
  if (part.tool === 'question' && typeof part.callID === 'string') {
@@ -258,6 +268,9 @@ export function openCodePatchToolPayload() {
258
268
  truncated: false
259
269
  };
260
270
  }
271
+ export function openCodeSourceItemId(messageId, partId) {
272
+ return JSON.stringify(['opencode', messageId, partId]);
273
+ }
261
274
  function item(session, turnId, nativeId, kind, status, payload, startedAt, completedAt, sequence) {
262
275
  return {
263
276
  id: boundedConversationItemId(`${session.id}:official:${turnId}`, nativeId),
@@ -1,4 +1,4 @@
1
- import { OpenCodeUsageNormalizer } from './conversation-parser.js';
1
+ import { OpenCodeUsageNormalizer, openCodeSourceItemId } from './conversation-parser.js';
2
2
  import { parseComposerTags } from '@myagentroam/protocol';
3
3
  import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
4
4
  import { parseSecretEnvironment } from '../../util/secret-environment.js';
@@ -440,7 +440,10 @@ export class OpenCodeManagedRunController {
440
440
  }
441
441
  const item = openCodePart(part, this.runner.execution.get(runId)?.agent === 'plan');
442
442
  if (item !== undefined)
443
- this.host.emitItem(runId, item);
443
+ this.host.emitItem(runId, {
444
+ ...item,
445
+ sourceItemId: openCodeSourceItemId(part.messageID, part.id)
446
+ });
444
447
  if (typeof part.id === 'string') {
445
448
  const itemId = boundedConversationItemId('opencode', part.id);
446
449
  const responseLatency = this.currentResponseMessage(runId, part.messageID)
@@ -688,6 +691,8 @@ function openCodePart(part, planMode = false) {
688
691
  const id = typeof part?.id === 'string' ? part.id : undefined;
689
692
  if (id === undefined || typeof part?.type !== 'string')
690
693
  return undefined;
694
+ if (part.type === 'reasoning' && (typeof part.text !== 'string' || !part.text.trim()))
695
+ return undefined;
691
696
  const itemId = boundedConversationItemId('opencode', id);
692
697
  if (part.type === 'text' && typeof part.text === 'string')
693
698
  return {
@@ -702,9 +707,9 @@ function openCodePart(part, planMode = false) {
702
707
  if (part.type === 'reasoning' && typeof part.text === 'string')
703
708
  return {
704
709
  itemId,
705
- kind: 'reasoning_summary',
710
+ kind: 'reasoning',
706
711
  status: record(part.time)?.end === undefined ? 'IN_PROGRESS' : 'COMPLETED',
707
- payload: { sections: [{ index: 0, text: part.text }] },
712
+ payload: { representation: 'unspecified', sections: [{ index: 0, text: part.text }] },
708
713
  merge: true
709
714
  };
710
715
  if (part.type === 'tool' && typeof part.tool === 'string') {
@@ -38,6 +38,16 @@ export function normalizeOpenCodeTool(toolName, state) {
38
38
  progressLabel: null,
39
39
  errorCode: status === 'FAILED' ? 'OPENCODE_TOOL_FAILED' : null,
40
40
  truncated: false,
41
+ ...(normalizedName === 'read'
42
+ ? {
43
+ toolPresentation: {
44
+ kind: 'READ',
45
+ paths: typeof input?.filePath === 'string' && input.filePath.length > 0
46
+ ? [compactRunnerText(input.filePath, 4096)]
47
+ : []
48
+ }
49
+ }
50
+ : {}),
41
51
  ...subagent
42
52
  }
43
53
  };
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { projectConversationWorkProcesses } from './service/conversation-work-process.js';
2
3
  import { nodeLog } from './operational.js';
3
4
  import { safeErrorCode } from './util/safe-error.js';
4
5
  import { permissionStopSchema } from '@myagentroam/protocol';
@@ -1383,6 +1384,7 @@ export class NodeRuntimeState {
1383
1384
  this.currentTurnForRun(event.runId) === undefined &&
1384
1385
  conversationItemFromEvent(event) !== undefined)
1385
1386
  this.createItemOnlyTurn(run, event.createdAt);
1387
+ let needsProcessProjection = false;
1386
1388
  const update = (turn) => {
1387
1389
  if (event.eventType === 'text.delta' &&
1388
1390
  isRecord(event.payload) &&
@@ -1398,8 +1400,14 @@ export class NodeRuntimeState {
1398
1400
  : undefined
1399
1401
  : turn.items.find((item) => item.id === stableItemId && item.kind === 'assistant_message');
1400
1402
  const text = `${existing === undefined ? '' : readableText(existing.payload)}${event.payload.text}`;
1403
+ const sourceItemId = conversationSourceItemId(event.payload) ?? existing?.sourceItemId;
1404
+ needsProcessProjection =
1405
+ existing === undefined ||
1406
+ existing.sourceItemId !== sourceItemId ||
1407
+ (!readableText(existing.payload).trim() && !!text.trim());
1401
1408
  const item = {
1402
1409
  id: existing?.id ?? stableItemId ?? `${event.runId}:assistant:${event.sequence}`,
1410
+ ...(sourceItemId === undefined ? {} : { sourceItemId }),
1403
1411
  sessionId: run.sessionId,
1404
1412
  turnId: turn.id,
1405
1413
  runId: event.runId,
@@ -1407,6 +1415,7 @@ export class NodeRuntimeState {
1407
1415
  sourceSequence: existing?.sourceSequence ?? event.sequence,
1408
1416
  revision: event.sequence,
1409
1417
  kind: 'assistant_message',
1418
+ ...(existing?.processGroup === undefined ? {} : { processGroup: existing.processGroup }),
1410
1419
  updatedAt: event.createdAt,
1411
1420
  status: 'IN_PROGRESS',
1412
1421
  payload: { text, format: 'markdown', model: null },
@@ -1447,15 +1456,30 @@ export class NodeRuntimeState {
1447
1456
  ? trailingItem
1448
1457
  : undefined);
1449
1458
  const status = projected.status;
1459
+ const sourceItemId = projected.sourceItemId ?? existing?.sourceItemId;
1460
+ const parentItemId = projected.parentItemId === undefined
1461
+ ? (existing?.parentItemId ?? null)
1462
+ : runConversationItemId(event.runId, projected.parentItemId);
1463
+ needsProcessProjection =
1464
+ existing === undefined ||
1465
+ existing.kind !== projected.kind ||
1466
+ projected.kind === 'context_compaction' ||
1467
+ existing.parentItemId !== parentItemId ||
1468
+ existing.sourceItemId !== sourceItemId ||
1469
+ (projected.kind === 'assistant_message' &&
1470
+ !!readableText(existing.payload).trim() !== !!readableText(projected.payload).trim()) ||
1471
+ (projected.kind === 'reasoning' && existing.processGroup === undefined);
1450
1472
  const item = {
1451
1473
  id: itemId,
1452
1474
  sessionId: run.sessionId,
1453
1475
  turnId: turn.id,
1454
1476
  runId: event.runId,
1455
- parentItemId: null,
1477
+ parentItemId,
1478
+ ...(sourceItemId === undefined ? {} : { sourceItemId }),
1456
1479
  sourceSequence: existing?.sourceSequence ?? event.sequence,
1457
1480
  revision: (existing?.revision ?? -1) + 1,
1458
1481
  kind: projected.kind,
1482
+ ...(existing?.processGroup === undefined ? {} : { processGroup: existing.processGroup }),
1459
1483
  updatedAt: event.createdAt,
1460
1484
  status,
1461
1485
  payload: mergeConversationItemPayload(existing?.payload, projected),
@@ -1495,11 +1519,16 @@ export class NodeRuntimeState {
1495
1519
  }
1496
1520
  return turn;
1497
1521
  };
1522
+ const project = (turn) => {
1523
+ const next = update(turn);
1524
+ // Ordinary token/output updates retain membership; only structural input needs a scan.
1525
+ return needsProcessProjection ? projectConversationWorkProcesses(next) : next;
1526
+ };
1498
1527
  if (turnId === undefined)
1499
- this.updateCurrentTurnForRun(event.runId, update);
1528
+ this.updateCurrentTurnForRun(event.runId, project);
1500
1529
  else {
1501
1530
  const current = this.turns.get(run.sessionId) ?? [];
1502
- this.turns.set(run.sessionId, current.map((turn) => (turn.id === turnId ? update(turn) : turn)));
1531
+ this.turns.set(run.sessionId, current.map((turn) => (turn.id === turnId ? project(turn) : turn)));
1503
1532
  }
1504
1533
  }
1505
1534
  }
@@ -1575,12 +1604,19 @@ function conversationItemFromEvent(event) {
1575
1604
  !isRecord(payload))
1576
1605
  return undefined;
1577
1606
  const append = event.payload.append;
1607
+ const sourceItemId = conversationSourceItemId(event.payload);
1578
1608
  const permissionStop = permissionStopSchema.safeParse(event.payload.permissionStop);
1579
1609
  return {
1580
1610
  itemId,
1581
1611
  kind,
1582
1612
  status,
1583
1613
  payload,
1614
+ ...(sourceItemId === undefined ? {} : { sourceItemId }),
1615
+ ...(typeof event.payload.parentItemId === 'string' &&
1616
+ event.payload.parentItemId.length > 0 &&
1617
+ event.payload.parentItemId.length <= 88
1618
+ ? { parentItemId: event.payload.parentItemId }
1619
+ : {}),
1584
1620
  ...(permissionStop.success ? { permissionStop: permissionStop.data } : {}),
1585
1621
  merge: event.payload.merge === true,
1586
1622
  ...(append === 'sections' || append === 'outputPreview' || append === 'outputSummary'
@@ -1588,10 +1624,17 @@ function conversationItemFromEvent(event) {
1588
1624
  : {})
1589
1625
  };
1590
1626
  }
1627
+ function conversationSourceItemId(payload) {
1628
+ return typeof payload.sourceItemId === 'string' &&
1629
+ payload.sourceItemId.length > 0 &&
1630
+ payload.sourceItemId.length <= 1024
1631
+ ? payload.sourceItemId
1632
+ : undefined;
1633
+ }
1591
1634
  function projectableConversationKind(value) {
1592
1635
  return (value === 'user_message' ||
1593
1636
  value === 'assistant_message' ||
1594
- value === 'reasoning_summary' ||
1637
+ value === 'reasoning' ||
1595
1638
  value === 'tool_call' ||
1596
1639
  value === 'command_execution' ||
1597
1640
  value === 'approval_request' ||
@@ -1,3 +1,4 @@
1
+ import { projectConversationWorkProcesses } from './conversation-work-process.js';
1
2
  import { discoverNativeSessions, readNativeSession } from '../native-session-history.js';
2
3
  import { nativeConversationPage } from '../util/runner-native-session-parsers.js';
3
4
  import { isPlainRecord } from '../util/node-operation-parsers.js';
@@ -552,7 +553,7 @@ export class ConversationHistoryService {
552
553
  page(nextCursor, turns, metadata) {
553
554
  return {
554
555
  ...metadata,
555
- turns,
556
+ turns: turns.map(projectConversationWorkProcesses),
556
557
  nextCursor,
557
558
  latestVisibleAt: Math.max(0, ...turns.map((turn) => Math.max(turn.startedAt ?? 0, turn.completedAt ?? 0)))
558
559
  };
@@ -1,3 +1,4 @@
1
+ import { projectConversationWorkProcesses } from './conversation-work-process.js';
1
2
  const MAX_INITIAL_TURN_PAGE_SIZE = 30;
2
3
  function conversationSegmentPrefix(turn) {
3
4
  const user = turn.items.find((item) => item.kind === 'user_message');
@@ -10,6 +11,7 @@ function conversationSegmentPrefix(turn) {
10
11
  : turn.id;
11
12
  }
12
13
  export function conversationSegments(turn) {
14
+ turn = projectConversationWorkProcesses(turn);
13
15
  const prefix = conversationSegmentPrefix(turn);
14
16
  const groups = [];
15
17
  let current = [];
@@ -25,7 +27,7 @@ export function conversationSegments(turn) {
25
27
  return groups.map((items, index) => {
26
28
  const id = `${prefix}:segment:${index}`;
27
29
  const latest = index === groups.length - 1;
28
- const projectedItems = items.map((item) => ({
30
+ const projectedItems = items.map(({ sourceItemId: _sourceItemId, ...item }) => ({
29
31
  ...item,
30
32
  runId: turn.runId,
31
33
  segmentId: id,
@@ -0,0 +1,102 @@
1
+ import { createHash } from 'node:crypto';
2
+ const members = new Set([
3
+ 'reasoning',
4
+ 'tool_call',
5
+ 'command_execution',
6
+ 'file_change',
7
+ 'todo_list'
8
+ ]);
9
+ const boundaries = new Set([
10
+ 'user_message',
11
+ 'assistant_message',
12
+ 'approval_request',
13
+ 'user_input_request',
14
+ 'plan',
15
+ 'turn_summary'
16
+ ]);
17
+ function record(value) {
18
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
19
+ ? value
20
+ : {};
21
+ }
22
+ function visible(item) {
23
+ const payload = record(item.payload);
24
+ if (item.kind === 'assistant_message')
25
+ return typeof payload.text === 'string' && !!payload.text.trim();
26
+ if (item.kind === 'reasoning')
27
+ return (Array.isArray(payload.sections) &&
28
+ payload.sections.some((part) => {
29
+ const text = record(part).text;
30
+ return typeof text === 'string' && !!text.trim();
31
+ }));
32
+ return true;
33
+ }
34
+ /** Runtime owns this exact prefix; native history already carries the unprefixed identity. */
35
+ function sourceIdentity(item) {
36
+ if (item.sourceItemId !== undefined)
37
+ return item.sourceItemId;
38
+ const prefix = item.runId === null ? undefined : `${item.runId}:`;
39
+ return prefix !== undefined && item.id.startsWith(prefix)
40
+ ? item.id.slice(prefix.length)
41
+ : item.id;
42
+ }
43
+ /** Project complete public input before transport segmentation, without clocks or stored groups. */
44
+ export function projectConversationWorkProcesses(turn) {
45
+ const positions = new Map(turn.items.map((item, index) => [item.id, index]));
46
+ const anchored = new Map();
47
+ const relocated = new Set();
48
+ for (const item of turn.items) {
49
+ const payload = record(item.payload);
50
+ if (item.kind !== 'context_compaction' ||
51
+ !['BACKGROUND', 'BLOCKING'].includes(String(payload.mode)) ||
52
+ typeof payload.anchorItemId !== 'string' ||
53
+ !positions.has(payload.anchorItemId) ||
54
+ payload.anchorItemId === item.id ||
55
+ turn.items[positions.get(payload.anchorItemId)]?.kind === 'context_compaction')
56
+ continue;
57
+ const list = anchored.get(payload.anchorItemId) ?? [];
58
+ list.push(item);
59
+ anchored.set(payload.anchorItemId, list);
60
+ relocated.add(item.id);
61
+ }
62
+ const ordered = turn.items.flatMap((item) => relocated.has(item.id) ? [] : [item, ...(anchored.get(item.id) ?? [])]);
63
+ let knownBoundary = turn.runId !== null || turn.items.some((item) => item.id === turn.userItemId);
64
+ let current;
65
+ const assigned = new Map();
66
+ const items = ordered.map((item) => {
67
+ const payload = record(item.payload);
68
+ let group;
69
+ let isStart = false;
70
+ const independent = boundaries.has(item.kind) ||
71
+ (item.kind === 'context_compaction' && payload.mode !== 'BACKGROUND') ||
72
+ (item.kind === 'error' && item.parentItemId === null);
73
+ if (visible(item)) {
74
+ if (independent) {
75
+ current = undefined;
76
+ knownBoundary = true;
77
+ }
78
+ else if (members.has(item.kind) || ['warning', 'error', 'unknown'].includes(item.kind)) {
79
+ group = item.parentItemId === null ? undefined : assigned.get(item.parentItemId);
80
+ if (group === undefined) {
81
+ group = current;
82
+ if (group === undefined && knownBoundary && members.has(item.kind)) {
83
+ group = `process:${createHash('sha256')
84
+ .update(`${turn.sessionId}\0${sourceIdentity(item)}`)
85
+ .digest('hex')}`;
86
+ current = group;
87
+ isStart = true;
88
+ }
89
+ }
90
+ if (group !== undefined)
91
+ assigned.set(item.id, group);
92
+ }
93
+ }
94
+ if (item.processGroup?.id === group && (item.processGroup?.isStart === true) === isStart)
95
+ return item;
96
+ const { processGroup: _previous, ...rest } = item;
97
+ return group === undefined
98
+ ? rest
99
+ : { ...rest, processGroup: { id: group, ...(isStart ? { isStart: true } : {}) } };
100
+ });
101
+ return items.every((item, index) => item === turn.items[index]) ? turn : { ...turn, items };
102
+ }
@@ -159,8 +159,15 @@ export class RunEventService {
159
159
  const createdTurn = targetsCurrentTurn && targetTurn === undefined && currentTurn === undefined
160
160
  ? this.options.runtime.conversationTurnForRun(runId)
161
161
  : undefined;
162
+ const projectedTurn = this.options.runtime.conversationTurnForExecution(executionId) ??
163
+ this.options.runtime.conversationTurnForRun(runId);
164
+ const previousGroups = new Map(targetTurn?.items.map((item) => [item.id, item.processGroup]));
165
+ const correctedGroups = projectedTurn !== undefined &&
166
+ projectedTurn.items.some((item) => previousGroups.has(item.id) &&
167
+ (previousGroups.get(item.id)?.id !== item.processGroup?.id ||
168
+ previousGroups.get(item.id)?.isStart !== item.processGroup?.isStart));
162
169
  if (targetsCurrentTurn)
163
- this.options.emitWorkbench('conversation', createdTurn === undefined
170
+ this.options.emitWorkbench('conversation', createdTurn === undefined && !correctedGroups
164
171
  ? {
165
172
  event: {
166
173
  ...event,
@@ -168,7 +175,7 @@ export class RunEventService {
168
175
  createdAt: recorded?.createdAt ?? Date.now()
169
176
  }
170
177
  }
171
- : { turn: createdTurn });
178
+ : { turn: createdTurn ?? projectedTurn });
172
179
  else {
173
180
  const projectedTurn = this.options.runtime.conversationTurnForExecution(executionId);
174
181
  if (projectedTurn !== undefined)
@@ -90,6 +90,8 @@ export function projectWorkbenchPayload(queries, category, payload) {
90
90
  payload: item.payload,
91
91
  startedAt: item.startedAt,
92
92
  completedAt: item.completedAt,
93
+ parentItemId: item.parentItemId,
94
+ ...(item.processGroup === undefined ? {} : { processGroup: item.processGroup }),
93
95
  segmentId: segment.id,
94
96
  segmentIndex: segment.index
95
97
  }
@@ -29,7 +29,7 @@ export class WorkbenchManifestService {
29
29
  conversation: {
30
30
  available: true,
31
31
  reasonCode: null,
32
- itemVersions: [1],
32
+ itemVersions: [2],
33
33
  supportsSessionCursor: true,
34
34
  supportsHistoryPaging: true,
35
35
  supportsItemFinalSnapshots: true,
@@ -133,16 +133,6 @@ export function extractClaudeText(message) {
133
133
  .flatMap((block) => block.type === 'text' && typeof block.text === 'string' ? [block.text] : [])
134
134
  .join('');
135
135
  }
136
- export function extractClaudeThinkingSummaries(message) {
137
- return claudeContentBlocks(message).flatMap((block) => {
138
- // `showThinkingSummaries` asks the SDK/API for exactly these public
139
- // summaries. Partial raw thinking events are intentionally not read.
140
- if (block.type !== 'thinking' || typeof block.thinking !== 'string')
141
- return [];
142
- const text = compactRunnerText(block.thinking.trim(), 10_000);
143
- return text.length === 0 ? [] : [text];
144
- });
145
- }
146
136
  export function extractClaudeToolUses(message) {
147
137
  return claudeContentBlocks(message).flatMap((block) => {
148
138
  if (block.type !== 'tool_use' || typeof block.id !== 'string' || typeof block.name !== 'string')
@@ -263,7 +263,9 @@ function nativeTranscriptTurnIds(sessionId, turns) {
263
263
  return turns.map((entries) => {
264
264
  const user = entries.find(({ entry }) => entry.kind === 'message' && entry.role === 'USER')?.entry;
265
265
  const first = user ?? entries[0]?.entry;
266
- const base = first?.nativeId ?? nativeTranscriptItemFingerprint(first);
266
+ const base = first?.nativeId ??
267
+ nativeTranscriptSourceIdentity(first) ??
268
+ nativeTranscriptItemFingerprint(first);
267
269
  const occurrence = occurrences.get(base) ?? 0;
268
270
  occurrences.set(base, occurrence + 1);
269
271
  return `${sessionId}:native:${base}${occurrence === 0 ? '' : `:${occurrence}`}`;
@@ -276,14 +278,20 @@ function nativeTranscriptItemIdentities(entries) {
276
278
  nativeIdCounts.set(entry.nativeId, (nativeIdCounts.get(entry.nativeId) ?? 0) + 1);
277
279
  const occurrences = new Map();
278
280
  return entries.map(({ entry }) => {
279
- const base = entry.nativeId !== undefined && nativeIdCounts.get(entry.nativeId) === 1
280
- ? entry.nativeId
281
- : `${entry.nativeId === undefined ? '' : `${entry.nativeId}:`}${nativeTranscriptItemFingerprint(entry)}`;
281
+ const base = nativeTranscriptSourceIdentity(entry) ??
282
+ (entry.nativeId !== undefined && nativeIdCounts.get(entry.nativeId) === 1
283
+ ? entry.nativeId
284
+ : `${entry.nativeId === undefined ? '' : `${entry.nativeId}:`}${nativeTranscriptItemFingerprint(entry)}`);
282
285
  const occurrence = occurrences.get(base) ?? 0;
283
286
  occurrences.set(base, occurrence + 1);
284
287
  return occurrence === 0 ? base : `${base}:${occurrence}`;
285
288
  });
286
289
  }
290
+ function nativeTranscriptSourceIdentity(entry) {
291
+ return entry?.sourceItemId === undefined
292
+ ? undefined
293
+ : `source:${createHash('sha256').update(entry.sourceItemId).digest('base64url').slice(0, 24)}`;
294
+ }
287
295
  function nativeTranscriptItemFingerprint(entry) {
288
296
  return createHash('sha256')
289
297
  .update(JSON.stringify(entry ?? null))
@@ -350,6 +358,7 @@ function nativeTranscriptConversationItem(session, entry, index, itemIdentity, t
350
358
  const planText = entry.kind === 'message' && entry.role !== 'USER' ? claudePlanText(entryText) : undefined;
351
359
  const item = {
352
360
  id: `${turnId}:item:${itemIdentity}`,
361
+ ...(entry.sourceItemId === undefined ? {} : { sourceItemId: entry.sourceItemId }),
353
362
  sessionId: session.id,
354
363
  turnId,
355
364
  runId: null,
@@ -359,7 +368,7 @@ function nativeTranscriptConversationItem(session, entry, index, itemIdentity, t
359
368
  kind: isUserInputRequest
360
369
  ? 'user_input_request'
361
370
  : isReasoning
362
- ? 'reasoning_summary'
371
+ ? 'reasoning'
363
372
  : isContextCompaction
364
373
  ? 'context_compaction'
365
374
  : isToolCall
@@ -378,7 +387,7 @@ function nativeTranscriptConversationItem(session, entry, index, itemIdentity, t
378
387
  questions: userInputQuestions
379
388
  }
380
389
  : isReasoning
381
- ? { sections: entry.sections }
390
+ ? { representation: entry.representation ?? 'unspecified', sections: entry.sections }
382
391
  : isContextCompaction
383
392
  ? { phase: entry.phase, summary: entry.summary }
384
393
  : isToolCall
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -37,10 +37,10 @@
37
37
  "playwright": "1.62.1",
38
38
  "ws": "^8.21.3",
39
39
  "zod": "4.4.3",
40
- "@myagentroam/agent": "0.11.2",
41
- "@myagentroam/protocol": "0.11.2",
40
+ "@myagentroam/agent": "0.11.4",
41
+ "@myagentroam/code-navigation": "0.11.0",
42
42
  "@myagentroam/marctl": "0.11.0",
43
- "@myagentroam/code-navigation": "0.11.0"
43
+ "@myagentroam/protocol": "0.11.4"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/ws": "^8.18.1"