@myagentroam/node 0.11.2 → 0.11.3
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/native-session-history.js +31 -1
- package/dist/runner/claude/conversation-parser.js +42 -0
- package/dist/runner/claude/managed-run-controller.js +78 -60
- package/dist/runner/claude/tool-normalizer.js +10 -0
- package/dist/runner/codex/conversation-parser.js +18 -3
- package/dist/runner/codex/managed-run-controller.js +25 -10
- package/dist/runner/mar-agent/conversation-normalizer.js +23 -9
- package/dist/runner/mar-agent/history-projection.js +3 -3
- package/dist/runner/mar-agent/managed-run-controller.js +1 -0
- package/dist/runner/opencode/conversation-parser.js +16 -3
- package/dist/runner/opencode/managed-run-controller.js +9 -4
- package/dist/runner/opencode/tool-normalizer.js +10 -0
- package/dist/runtime-state.js +47 -4
- package/dist/service/conversation-history-service.js +2 -1
- package/dist/service/conversation-segment-service.js +3 -1
- package/dist/service/conversation-work-process.js +102 -0
- package/dist/service/run-event-service.js +9 -2
- package/dist/service/workbench-event-projection.js +2 -0
- package/dist/service/workbench-manifest-service.js +1 -1
- package/dist/util/node-operation-parsers.js +0 -10
- package/dist/util/runner-native-session-parsers.js +15 -6
- package/package.json +5 -5
|
@@ -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,
|
|
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
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
this.host.
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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: '
|
|
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: '
|
|
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.
|
|
260
|
+
this.emitItem(runId, {
|
|
249
261
|
itemId: boundedConversationItemId('codex', params.itemId),
|
|
250
|
-
kind: '
|
|
262
|
+
kind: 'reasoning',
|
|
251
263
|
status: 'IN_PROGRESS',
|
|
252
|
-
payload: {
|
|
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.
|
|
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.
|
|
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.
|
|
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', '
|
|
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.
|
|
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.
|
|
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.
|
|
81
|
+
if (event.type === 'reasoning.completed')
|
|
82
82
|
return [
|
|
83
83
|
{
|
|
84
|
-
itemId: itemId('reasoning', event.
|
|
85
|
-
kind: '
|
|
84
|
+
itemId: itemId('reasoning', event.itemId),
|
|
85
|
+
kind: 'reasoning',
|
|
86
86
|
status: 'COMPLETED',
|
|
87
|
-
payload: {
|
|
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
|
-
//
|
|
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(
|
|
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') : '
|
|
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
|
-
: {
|
|
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,
|
|
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: '
|
|
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
|
};
|
package/dist/runtime-state.js
CHANGED
|
@@ -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
|
|
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,
|
|
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 ?
|
|
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 === '
|
|
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
|
}
|
|
@@ -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 ??
|
|
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 =
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
? '
|
|
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.
|
|
3
|
+
"version": "0.11.3",
|
|
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.
|
|
41
|
-
"@myagentroam/
|
|
42
|
-
"@myagentroam/
|
|
43
|
-
"@myagentroam/
|
|
40
|
+
"@myagentroam/agent": "0.11.3",
|
|
41
|
+
"@myagentroam/code-navigation": "0.11.0",
|
|
42
|
+
"@myagentroam/protocol": "0.11.3",
|
|
43
|
+
"@myagentroam/marctl": "0.11.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/ws": "^8.18.1"
|