@myagentroam/node 0.11.44 → 0.11.45

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.
@@ -0,0 +1,167 @@
1
+ import { agentOperationPresentationSchema } from '@myagentroam/protocol';
2
+ import { isPlainRecord } from '../../util/records.js';
3
+ const actions = {
4
+ agent_start: 'START',
5
+ agent_send: 'SEND',
6
+ agent_wait: 'WAIT',
7
+ agent_stop: 'STOP',
8
+ agent_list: 'LIST',
9
+ agent_view: 'VIEW'
10
+ };
11
+ export function agentOperationAction(toolName) {
12
+ return Object.hasOwn(actions, toolName) ? actions[toolName] : undefined;
13
+ }
14
+ function text(value, max = 128) {
15
+ return typeof value === 'string' && value.trim()
16
+ ? value.slice(0, max).replace(/[\uD800-\uDBFF]$/u, '')
17
+ : undefined;
18
+ }
19
+ function latestWaitMessage(updates) {
20
+ if (!Array.isArray(updates))
21
+ return undefined;
22
+ const message = updates.findLast((entry) => isPlainRecord(entry) && entry.kind === 'message' && typeof entry.value === 'string');
23
+ if (!isPlainRecord(message))
24
+ return undefined;
25
+ const value = text(message.value, 800);
26
+ if (!value)
27
+ return undefined;
28
+ return {
29
+ value,
30
+ ...(message.final === true ? { final: true } : {}),
31
+ ...(message.truncated === true || message.value.length > 800
32
+ ? { truncated: true }
33
+ : {})
34
+ };
35
+ }
36
+ /** Input identities are display-only; only successful Node results establish navigation. */
37
+ export function agentOperationPresentation(toolName, input, data) {
38
+ const action = agentOperationAction(toolName);
39
+ if (!action)
40
+ return undefined;
41
+ const parsed = agentOperationPresentationSchema.safeParse(isPlainRecord(data) ? data.agentOperation : undefined);
42
+ if (parsed.success && parsed.data.action === action)
43
+ return parsed.data;
44
+ const value = isPlainRecord(input) ? input : {};
45
+ const ids = typeof value.agentId === 'string'
46
+ ? [value.agentId]
47
+ : Array.isArray(value.agentIds)
48
+ ? value.agentIds
49
+ : [];
50
+ return {
51
+ action,
52
+ targets: ids
53
+ .filter((id) => typeof id === 'string' && id.length > 0 && id.length <= 128)
54
+ .slice(0, 8)
55
+ .map((agentId) => ({ agentId })),
56
+ ...(text(value.scope, 32) ? { scope: text(value.scope, 32) } : {})
57
+ };
58
+ }
59
+ /** Whitelist public receipts; never serialize configuration, credentials or full history. */
60
+ export function agentOperationEvent(action, input, result) {
61
+ const operation = agentOperationPresentation(`agent_${action}`, input);
62
+ const value = isPlainRecord(result) ? result : {};
63
+ const args = isPlainRecord(input) ? input : {};
64
+ const rows = action === 'list' ? value.sessions : action === 'wait' ? value.agents : undefined;
65
+ const records = Array.isArray(rows)
66
+ ? rows
67
+ : action === 'list'
68
+ ? []
69
+ : action === 'view'
70
+ ? isPlainRecord(value.session)
71
+ ? [value.session]
72
+ : []
73
+ : typeof value.agentId === 'string'
74
+ ? [value]
75
+ : [];
76
+ const returnedTargets = records
77
+ .flatMap((entry) => {
78
+ if (!isPlainRecord(entry))
79
+ return [];
80
+ const rawId = entry.agentId ?? entry.id;
81
+ const agentId = typeof rawId === 'string' && rawId.length <= 128 ? text(rawId) : undefined;
82
+ if (!agentId)
83
+ return [];
84
+ const title = text(entry.title ?? (action === 'start' ? args.title : undefined), 500);
85
+ return [{ agentId, sessionId: agentId, ...(title ? { title } : {}) }];
86
+ })
87
+ .slice(0, 100);
88
+ // An any-wait may return one result for several requested targets; keep the call multi-target.
89
+ const targets = action === 'wait' && operation.targets.length > 0
90
+ ? operation.targets.map((target) => returnedTargets.find((result) => result.agentId === target.agentId) ?? target)
91
+ : returnedTargets;
92
+ const presentation = { ...operation, targets };
93
+ if (records.length > returnedTargets.length)
94
+ presentation.targetsTruncated = true;
95
+ // Core accepts at most 8 KiB of explicit extension event data.
96
+ while (Buffer.byteLength(JSON.stringify({ agentOperation: presentation }), 'utf8') > 7000) {
97
+ targets.pop();
98
+ presentation.targetsTruncated = true;
99
+ }
100
+ const receipt = {};
101
+ if (action === 'list' && Array.isArray(value.models)) {
102
+ receipt.runner = text(value.runner);
103
+ receipt.modelCount = value.models.length;
104
+ }
105
+ if (action === 'view' && isPlainRecord(value.session)) {
106
+ for (const key of ['status', 'model', 'effort']) {
107
+ const field = text(value.session[key]);
108
+ if (field)
109
+ receipt[key] = field;
110
+ }
111
+ const message = isPlainRecord(value.session.latestMessage)
112
+ ? value.session.latestMessage
113
+ : undefined;
114
+ const messageText = text(message?.text, 800);
115
+ if (messageText)
116
+ receipt.latestMessage = {
117
+ text: messageText,
118
+ truncated: message?.truncated === true || messageText !== message?.text
119
+ };
120
+ }
121
+ for (const key of ['status', 'waitOutcome', 'waitFor', 'mode', 'reason', 'executionId']) {
122
+ const field = text(value[key]);
123
+ if (field)
124
+ receipt[key] = field;
125
+ }
126
+ for (const key of ['message', 'cancel', 'goal', 'approval', 'observation']) {
127
+ const item = value[key];
128
+ if (!isPlainRecord(item))
129
+ continue;
130
+ receipt[key] = {
131
+ ...(text(item.status) ? { status: text(item.status) } : {}),
132
+ ...(text(item.code) ? { code: text(item.code) } : {}),
133
+ ...(isPlainRecord(item.result) && text(item.result.delivery)
134
+ ? { delivery: text(item.result.delivery) }
135
+ : {})
136
+ };
137
+ }
138
+ if (Array.isArray(rows))
139
+ receipt.count = rows.length;
140
+ if (action === 'wait' && Array.isArray(rows))
141
+ receipt.agents = rows.slice(0, 8).map((entry) => {
142
+ const agent = isPlainRecord(entry) ? entry : {};
143
+ return {
144
+ agentId: text(agent.agentId),
145
+ status: text(agent.status),
146
+ waitOutcome: text(agent.waitOutcome),
147
+ summary: text(agent.summary, 800),
148
+ latestMessage: latestWaitMessage(agent.updates),
149
+ ...(agent.summaryTruncated === true ||
150
+ (typeof agent.summary === 'string' && agent.summary.length > 800)
151
+ ? { summaryTruncated: true }
152
+ : {})
153
+ };
154
+ });
155
+ if (action === 'wait' && !Array.isArray(rows))
156
+ receipt.latestMessage = latestWaitMessage(value.updates);
157
+ const summary = text(value.summary, 4000);
158
+ if (summary)
159
+ receipt.summary = summary;
160
+ if (value.summaryTruncated === true ||
161
+ (typeof value.summary === 'string' && value.summary.length > 4000))
162
+ receipt.summaryTruncated = true;
163
+ return {
164
+ eventData: { agentOperation: presentation },
165
+ eventSummary: JSON.stringify(receipt)
166
+ };
167
+ }
@@ -7,8 +7,9 @@ export function normalizeMarAgentUsage(usage) {
7
7
  import { boundedConversationItemId, compactRunnerText, compactRunnerValue } from '../normalization.js';
8
8
  import { runnerErrorProjection } from '../runner-error.js';
9
9
  import { toolPresentationSchema } from '@myagentroam/protocol';
10
- import { boundExecutionPresentation } from '../execution-presentation.js';
11
- import { publicOutputSnapshot } from '../normalization.js';
10
+ import { publicOutputSnapshot, publicToolOutput } from '../normalization.js';
11
+ import { boundToolOutput } from '@myagentroam/protocol';
12
+ import { agentOperationAction, agentOperationPresentation } from './agent-operation-presentation.js';
12
13
  function boundCodeOutputSnapshots(sources) {
13
14
  for (const channel of ['outputPreview', 'stdout', 'stderr']) {
14
15
  const text = (entry) => channel === 'outputPreview' ? entry.outputPreview : entry.displayOutput[channel];
@@ -147,15 +148,18 @@ export class MarAgentConversationNormalizer {
147
148
  this.#finishCodeModeOutputCall(event.callId, event.parentCallId);
148
149
  return [];
149
150
  }
150
- // Read/web calls and generated artifacts stay visible independently of cell observations.
151
+ // Public Agent calls, read/web calls and artifacts do not depend on model observations.
151
152
  if (event.parentCallId !== undefined &&
152
153
  event.toolName !== 'read' &&
153
154
  event.toolName !== 'web_search' &&
154
155
  event.toolName !== 'web_fetch' &&
156
+ agentOperationAction(event.toolName) === undefined &&
155
157
  !isImageGenerationTool(event.toolName)) {
156
158
  this.#finishCodeModeOutputCall(event.callId, event.parentCallId);
157
159
  return [];
158
160
  }
161
+ if (event.parentCallId !== undefined && agentOperationAction(event.toolName) !== undefined)
162
+ this.#codeModeSourceCallIds.add(event.callId);
159
163
  if (event.type === 'tool.started')
160
164
  return this.#toolStarted(event);
161
165
  this.#finishToolCall(event);
@@ -252,6 +256,7 @@ export class MarAgentConversationNormalizer {
252
256
  input: event.input,
253
257
  itemId: ownItemId,
254
258
  structured: 'input' in event,
259
+ inputTruncated: event.truncated === true,
255
260
  executionId: event.executionId,
256
261
  ...(event.turnId === undefined ? {} : { turnId: event.turnId }),
257
262
  active: true,
@@ -264,7 +269,7 @@ export class MarAgentConversationNormalizer {
264
269
  if (event.toolName === 'apply_patch' && 'input' in event)
265
270
  return [fileChangeItem(ownItemId, 'IN_PROGRESS', [])];
266
271
  return [
267
- genericToolItem(ownItemId, event.toolName, 'IN_PROGRESS', event.input, null, null, event.truncated === true, event.data, subagentTitle, subagentDetails)
272
+ genericToolItem(ownItemId, event.toolName, 'IN_PROGRESS', event.input, null, null, event.truncated === true, event.data, subagentTitle, subagentDetails, undefined, undefined, event.truncated === true)
268
273
  ];
269
274
  }
270
275
  #toolOutput(event) {
@@ -279,14 +284,13 @@ export class MarAgentConversationNormalizer {
279
284
  stderrDroppedBytes: 0
280
285
  };
281
286
  const displayed = [displayOutput.stdout, displayOutput.stderr].filter(Boolean).join('\n');
282
- const bounded = publicOutputSnapshot(displayed, output.truncated ||
283
- displayOutput.stdoutDroppedBytes > 0 ||
284
- displayOutput.stderrDroppedBytes > 0);
287
+ const bounded = publicOutputSnapshot(displayed, event.outputTruncation !== undefined);
285
288
  const snapshot = {
286
289
  outputPreview: bounded.text,
287
290
  truncated: bounded.truncated || inputOutput.length > 10_000,
288
291
  displayOutput,
289
- processId: output.processId
292
+ processId: output.processId,
293
+ ...(event.outputTruncation ? { outputTruncation: event.outputTruncation } : {})
290
294
  };
291
295
  if (parentCallId !== undefined && (toolName === 'exec' || toolName === 'exec_wait'))
292
296
  return this.#codeModeToolOutput(callId, parentCallId, toolName, executionId, turnId, output.processId, snapshot);
@@ -309,7 +313,7 @@ export class MarAgentConversationNormalizer {
309
313
  displayOutput: snapshot.displayOutput,
310
314
  outputHeadTruncated: snapshot.displayOutput.stdoutDroppedBytes > 0 ||
311
315
  snapshot.displayOutput.stderrDroppedBytes > 0
312
- })
316
+ }, undefined, undefined, undefined, event.outputTruncation, state.inputTruncated)
313
317
  ];
314
318
  }
315
319
  const command = this.#commands.get(state.commandOrigin);
@@ -320,6 +324,10 @@ export class MarAgentConversationNormalizer {
320
324
  command.displayOutput = display;
321
325
  command.outputPreview = snapshot.outputPreview;
322
326
  command.truncated = snapshot.truncated;
327
+ if (event.outputTruncation)
328
+ command.outputTruncation = event.outputTruncation;
329
+ else
330
+ delete command.outputTruncation;
323
331
  return [commandItem(command, 'IN_PROGRESS')];
324
332
  }
325
333
  #toolOutputDelta(event) {
@@ -375,26 +383,11 @@ export class MarAgentConversationNormalizer {
375
383
  const command = target.commandOrigin === undefined ? undefined : this.#commands.get(target.commandOrigin);
376
384
  const item = command
377
385
  ? commandItem(command, 'IN_PROGRESS')
378
- : genericToolItem(target.itemId, target.toolName, 'IN_PROGRESS', target.input, null, null);
379
- const original = record(record(item.payload.toolPresentation)?.execution);
386
+ : genericToolItem(target.itemId, target.toolName, 'IN_PROGRESS', target.input, null, null, false, undefined, undefined, undefined, undefined, undefined, target.inputTruncated);
380
387
  const blocks = [
381
388
  ...(output.stdout ? [{ channel: 'STDOUT', text: output.stdout }] : []),
382
389
  ...(output.stderr ? [{ channel: 'STDERR', text: output.stderr }] : [])
383
390
  ];
384
- const text = [output.stdout, output.stderr].filter(Boolean).join('\n');
385
- const boundedText = publicOutputSnapshot(text, output.truncated);
386
- const bounded = boundExecutionPresentation({
387
- kind: parentCallId !== undefined
388
- ? target.toolName === 'code_exec'
389
- ? 'CODE'
390
- : 'CONTROL'
391
- : toolName === 'exec'
392
- ? 'SHELL'
393
- : 'CONTROL',
394
- ...(typeof original?.toolName === 'string' ? { toolName: original.toolName } : {}),
395
- output: blocks,
396
- ...(output.truncated ? { truncation: { output: 'HEAD' } } : {})
397
- });
398
391
  return [
399
392
  {
400
393
  ...item,
@@ -402,9 +395,7 @@ export class MarAgentConversationNormalizer {
402
395
  merge: true,
403
396
  payload: {
404
397
  ...item.payload,
405
- outputPreview: boundedText.text,
406
- output: boundedText,
407
- toolPresentation: bounded.presentation
398
+ output: boundToolOutput(blocks, event.outputTruncation)
408
399
  }
409
400
  }
410
401
  ];
@@ -458,7 +449,13 @@ export class MarAgentConversationNormalizer {
458
449
  .filter(Boolean)
459
450
  .join('')
460
451
  }
461
- })
452
+ }, undefined, undefined, undefined, entries.some((entry) => entry.outputTruncation === 'SOURCE')
453
+ ? 'SOURCE'
454
+ : entries.some((entry) => entry.outputTruncation === 'HEAD' ||
455
+ entry.displayOutput.stdoutDroppedBytes > 0 ||
456
+ entry.displayOutput.stderrDroppedBytes > 0)
457
+ ? 'HEAD'
458
+ : undefined, target.inputTruncated)
462
459
  ];
463
460
  }
464
461
  #trackCodeModeOutputCall(event) {
@@ -626,9 +623,13 @@ export class MarAgentConversationNormalizer {
626
623
  const state = this.#tools.get(event.callId);
627
624
  if (event.toolName === 'exec' && state?.commandOrigin !== undefined) {
628
625
  const command = this.#commands.get(state.commandOrigin);
629
- const result = execResult(event.data, event.summary, event.truncated === true);
626
+ const result = execResult(event.data, event.summary, event.outputTruncation !== undefined);
630
627
  if (command !== undefined && result !== undefined) {
631
628
  applyCommandResult(command, result);
629
+ if (event.outputTruncation)
630
+ command.outputTruncation = event.outputTruncation;
631
+ else
632
+ delete command.outputTruncation;
632
633
  const data = record(event.data);
633
634
  const display = execDisplayOutput(data?.displayOutput);
634
635
  if (display !== undefined)
@@ -672,7 +673,7 @@ export class MarAgentConversationNormalizer {
672
673
  }
673
674
  const item = genericToolItem(state?.itemId ?? itemId('tool', event.callId), event.toolName, 'COMPLETED', state?.input, generatedImages.length === 0
674
675
  ? event.summary
675
- : generatedImages.map((image) => image.name).join(', '), null, event.truncated, event.data, agentId === undefined ? state?.subagentTitle : this.#subagentTitles.get(agentId), details, state?.sleepStartedAt);
676
+ : generatedImages.map((image) => image.name).join(', '), null, event.outputTruncation !== undefined, event.data, agentId === undefined ? state?.subagentTitle : this.#subagentTitles.get(agentId), details, state?.sleepStartedAt, event.outputTruncation, state?.inputTruncated, event.summaryTruncated === true);
676
677
  return [
677
678
  {
678
679
  ...item,
@@ -703,7 +704,7 @@ export class MarAgentConversationNormalizer {
703
704
  return [
704
705
  genericToolItem(state?.itemId ?? itemId('tool', event.callId), event.toolName, event.code === 'MAR_AGENT_CANCELLED' || event.code === 'MAR_AGENT_EXECUTION_CANCELLED'
705
706
  ? 'CANCELLED'
706
- : 'FAILED', state?.input, event.message ?? event.code, event.code, event.truncated === true, state?.presentationData, state?.subagentTitle, state?.subagentDetails, state?.sleepStartedAt)
707
+ : 'FAILED', state?.input, event.message ?? event.code, event.code, event.truncated === true, state?.presentationData, state?.subagentTitle, state?.subagentDetails, state?.sleepStartedAt, event.outputTruncation, state?.inputTruncated)
707
708
  ];
708
709
  }
709
710
  #codeModeToolResults(event) {
@@ -1026,33 +1027,30 @@ function codeExecutionError(data) {
1026
1027
  message: compactRunnerText(error.message, 1000)
1027
1028
  };
1028
1029
  }
1029
- function genericToolItem(id, toolName, status, input, outputSummary = null, errorCode = null, truncated = false, output, subagentTitle, knownSubagentDetails, sleepStartedAt) {
1030
+ function genericToolItem(id, toolName, status, input, outputSummary = null, errorCode = null, truncated = false, output, subagentTitle, knownSubagentDetails, sleepStartedAt, outputTruncation, inputTruncated = false, summaryTruncated = false) {
1030
1031
  if (toolName === 'code_exec') {
1031
1032
  const code = typeof input === 'string' ? input : record(input)?.code;
1032
1033
  const source = typeof code === 'string' ? code : compactRunnerValue(input, 10_000).text || toolName;
1033
1034
  const resultData = record(output);
1034
- const codeOutput = codeObservationOutput(resultData, outputSummary);
1035
+ const codeOutput = codeObservationOutput(resultData, null);
1035
1036
  const resultError = codeExecutionError(resultData);
1036
1037
  const preview = errorCode === null
1037
1038
  ? (outputSummary ?? '')
1038
1039
  : [errorCode, outputSummary].filter(Boolean).join(': ');
1039
- return commandItem({
1040
+ const item = commandItem({
1040
1041
  itemId: id,
1041
1042
  command: compactRunnerText(source, 10_000),
1042
1043
  cwd: null,
1043
- outputPreview: compactRunnerText(preview, 10_000),
1044
+ outputPreview: '',
1044
1045
  exitCode: null,
1045
- truncated: truncated ||
1046
- resultData?.truncated === true ||
1047
- source.length > 10_000 ||
1048
- preview.length > 10_000
1046
+ truncated: truncated || source.length > 10_000 || preview.length > 10_000
1049
1047
  }, status, 'Code execution', {
1050
1048
  kind: 'CODE',
1051
- ...(source.length > 10_000 || resultData?.outputHeadTruncated === true
1049
+ ...(source.length > 10_000 || inputTruncated || outputTruncation !== undefined
1052
1050
  ? {
1053
1051
  truncation: {
1054
- ...(source.length > 10_000 ? { input: true } : {}),
1055
- ...(resultData?.outputHeadTruncated === true ? { output: 'HEAD' } : {})
1052
+ ...(source.length > 10_000 || inputTruncated ? { input: true } : {}),
1053
+ ...(outputTruncation === undefined ? {} : { output: outputTruncation })
1056
1054
  }
1057
1055
  }
1058
1056
  : {}),
@@ -1068,6 +1066,12 @@ function genericToolItem(id, toolName, status, input, outputSummary = null, erro
1068
1066
  }),
1069
1067
  ...(codeOutput === undefined ? {} : { output: codeOutput })
1070
1068
  }, toolName);
1069
+ if (codeOutput === undefined && errorCode === null && outputSummary !== null) {
1070
+ item.payload.outputSummary = compactRunnerText(outputSummary, 10_000);
1071
+ if (summaryTruncated || outputSummary.length > 10_000)
1072
+ item.payload.outputSummaryTruncated = true;
1073
+ }
1074
+ return item;
1071
1075
  }
1072
1076
  if (toolName === 'code_exec_wait' || toolName === 'exec_wait') {
1073
1077
  const inputRecord = record(input);
@@ -1081,40 +1085,17 @@ function genericToolItem(id, toolName, status, input, outputSummary = null, erro
1081
1085
  ? [{ channel: 'STDERR', text: resultData.stderr }]
1082
1086
  : [])
1083
1087
  ];
1084
- const codeOutput = toolName === 'code_exec_wait' ? codeObservationOutput(resultData, outputSummary) : undefined;
1088
+ const codeOutput = toolName === 'code_exec_wait' ? codeObservationOutput(resultData, null) : undefined;
1085
1089
  const resultError = toolName === 'code_exec_wait' ? codeExecutionError(resultData) : undefined;
1086
1090
  const action = typeof inputRecord?.action === 'string' ? inputRecord.action : 'poll';
1087
1091
  const inputSummary = compactRunnerValue(input, 10_000);
1088
- const result = output === undefined ? null : compactRunnerValue(output, 10_000);
1089
- const resultSummary = [outputSummary, result?.text]
1090
- .filter((value, index, values) => typeof value === 'string' && value.length > 0 && values.indexOf(value) === index)
1091
- .join('\n');
1092
- const details = [
1093
- `Action: ${compactRunnerText(action, 128)}`,
1094
- `Input: ${inputSummary.text || '{}'}`,
1095
- resultSummary.length === 0 ? '' : `Result: ${compactRunnerText(resultSummary, 10_000)}`,
1096
- errorCode === null ? '' : `Error: ${compactRunnerText(errorCode, 80)}`
1097
- ]
1098
- .filter(Boolean)
1099
- .join('\n');
1100
- return commandItem({
1092
+ const item = commandItem({
1101
1093
  itemId: id,
1102
1094
  command: '',
1103
1095
  cwd: null,
1104
- outputPreview: status === 'IN_PROGRESS' && outputSummary !== null
1105
- ? outputSummary
1106
- : legacyBlocks.length > 0
1107
- ? legacyBlocks
1108
- .map((block) => block.text)
1109
- .filter(Boolean)
1110
- .join('\n')
1111
- : details,
1096
+ outputPreview: '',
1112
1097
  exitCode: typeof resultData?.exitCode === 'number' ? resultData.exitCode : null,
1113
- truncated: truncated ||
1114
- resultData?.truncated === true ||
1115
- inputSummary.truncated ||
1116
- result?.truncated === true ||
1117
- resultSummary.length > 10_000
1098
+ truncated: truncated || inputSummary.truncated
1118
1099
  }, status, toolName === 'code_exec_wait' ? 'Code execution wait' : 'Command wait', {
1119
1100
  kind: 'CONTROL',
1120
1101
  toolName,
@@ -1135,21 +1116,21 @@ function genericToolItem(id, toolName, status, input, outputSummary = null, erro
1135
1116
  }),
1136
1117
  ...(display === undefined && legacyBlocks.length > 0 ? { output: legacyBlocks } : {}),
1137
1118
  ...(codeOutput === undefined ? {} : { output: codeOutput }),
1138
- ...((typeof inputRecord?.data === 'string' && inputRecord.data.length > 10_000) ||
1139
- resultData?.outputHeadTruncated === true ||
1140
- (display !== undefined && (display.stdoutDroppedBytes || display.stderrDroppedBytes)) ||
1141
- (display === undefined && legacyBlocks.length > 0 && truncated)
1119
+ ...(inputTruncated ||
1120
+ (typeof inputRecord?.data === 'string' && inputRecord.data.length > 10_000) ||
1121
+ outputTruncation !== undefined ||
1122
+ (display !== undefined && (display.stdoutDroppedBytes || display.stderrDroppedBytes))
1142
1123
  ? {
1143
1124
  truncation: {
1144
- ...(typeof inputRecord?.data === 'string' && inputRecord.data.length > 10_000
1125
+ ...(inputTruncated ||
1126
+ (typeof inputRecord?.data === 'string' && inputRecord.data.length > 10_000)
1145
1127
  ? { input: true }
1146
1128
  : {}),
1147
- ...(resultData?.outputHeadTruncated === true ||
1148
- (display !== undefined &&
1149
- (display.stdoutDroppedBytes || display.stderrDroppedBytes))
1150
- ? { output: 'HEAD' }
1151
- : display === undefined && legacyBlocks.length > 0 && truncated
1152
- ? { output: 'SOURCE' }
1129
+ ...(outputTruncation !== undefined
1130
+ ? { output: outputTruncation }
1131
+ : display !== undefined &&
1132
+ (display.stdoutDroppedBytes || display.stderrDroppedBytes)
1133
+ ? { output: 'HEAD' }
1153
1134
  : {})
1154
1135
  }
1155
1136
  }
@@ -1165,8 +1146,32 @@ function genericToolItem(id, toolName, status, input, outputSummary = null, erro
1165
1146
  }
1166
1147
  })
1167
1148
  });
1149
+ if (display === undefined &&
1150
+ legacyBlocks.length === 0 &&
1151
+ codeOutput === undefined &&
1152
+ errorCode === null &&
1153
+ outputSummary !== null) {
1154
+ item.payload.outputSummary = compactRunnerText(outputSummary, 10_000);
1155
+ if (summaryTruncated || outputSummary.length > 10_000)
1156
+ item.payload.outputSummaryTruncated = true;
1157
+ }
1158
+ return item;
1168
1159
  }
1169
1160
  const subagent = marAgentSubagentProjection(toolName, input, output, subagentTitle, knownSubagentDetails);
1161
+ const agentOperation = agentOperationPresentation(toolName, input, output);
1162
+ if (agentOperation &&
1163
+ record(output)?.agentOperation === undefined &&
1164
+ typeof subagent?.subagentId === 'string') {
1165
+ agentOperation.targets = [
1166
+ {
1167
+ agentId: subagent.subagentId,
1168
+ ...(typeof subagent.subagentSessionId === 'string'
1169
+ ? { sessionId: subagent.subagentSessionId }
1170
+ : {}),
1171
+ ...(typeof subagent.title === 'string' ? { title: subagent.title } : {})
1172
+ }
1173
+ ];
1174
+ }
1170
1175
  const toolCategory = isImageGenerationTool(toolName) ? 'IMAGE_GENERATION' : undefined;
1171
1176
  const presentation = projectToolPresentation(toolName, input, output, sleepStartedAt);
1172
1177
  const inputSummary = compactRunnerValue(input, 10_000);
@@ -1179,13 +1184,20 @@ function genericToolItem(id, toolName, status, input, outputSummary = null, erro
1179
1184
  toolName: compactRunnerText(toolName, 128) || 'unknown',
1180
1185
  title: compactRunnerText(toolName, 500) || 'unknown',
1181
1186
  inputSummary: inputSummary.text || null,
1182
- outputSummary: outputSummary === null ? null : compactRunnerText(outputSummary, 10_000),
1183
- ...(outputSummary === null ? {} : { output: publicOutputSnapshot(outputSummary, truncated) }),
1187
+ outputSummary: typeof output === 'string' || errorCode !== null ? null : outputSummary,
1188
+ ...(typeof output === 'string' ? { output: publicToolOutput(output, outputTruncation) } : {}),
1189
+ ...(typeof output !== 'string' && outputSummary !== null && summaryTruncated
1190
+ ? { outputSummaryTruncated: true }
1191
+ : {}),
1184
1192
  progressLabel: status === 'IN_PROGRESS' ? 'Running' : null,
1185
1193
  errorCode: errorCode === null ? null : compactRunnerText(errorCode, 80),
1186
- truncated: truncated || inputSummary.truncated || (outputSummary?.length ?? 0) > 10_000,
1194
+ ...(errorCode !== null && outputSummary
1195
+ ? { errorMessage: compactRunnerText(outputSummary, 1000) }
1196
+ : {}),
1197
+ ...(inputTruncated || inputSummary.truncated ? { inputTruncated: true } : {}),
1187
1198
  ...(toolCategory === undefined ? {} : { toolCategory }),
1188
1199
  ...(presentation === undefined ? {} : { toolPresentation: presentation }),
1200
+ ...(agentOperation === undefined ? {} : { agentOperation }),
1189
1201
  ...subagent
1190
1202
  },
1191
1203
  merge: true
@@ -1294,7 +1306,14 @@ function commandItem(command, status, title, execution, toolName = 'exec') {
1294
1306
  ]
1295
1307
  })
1296
1308
  };
1297
- const bounded = boundExecutionPresentation({ toolName, ...detail });
1309
+ const { output: detailOutput, truncation, ...executionMetadata } = detail;
1310
+ const blocks = detailOutput ??
1311
+ (!detail.error && command.outputPreview
1312
+ ? [{ channel: 'COMBINED', text: command.outputPreview }]
1313
+ : undefined);
1314
+ const output = blocks === undefined
1315
+ ? undefined
1316
+ : boundToolOutput(blocks, command.outputTruncation ?? truncation?.output);
1298
1317
  return {
1299
1318
  itemId: command.itemId,
1300
1319
  kind: 'command_execution',
@@ -1303,22 +1322,17 @@ function commandItem(command, status, title, execution, toolName = 'exec') {
1303
1322
  ...(title === undefined ? {} : { title }),
1304
1323
  command: command.command,
1305
1324
  cwd: command.cwd,
1306
- outputPreview: command.outputPreview,
1307
- output: {
1308
- text: publicOutputSnapshot(bounded.presentation.execution.output
1309
- ?.map((block) => block.text)
1310
- .filter(Boolean)
1311
- .join('\n') ?? command.outputPreview).text,
1312
- truncated: command.truncated || bounded.truncated
1313
- },
1325
+ ...(output === undefined ? {} : { output }),
1314
1326
  exitCode: command.exitCode,
1315
1327
  durationMs: null,
1316
- truncated: command.truncated ||
1317
- command.inputTruncated === true ||
1318
- bounded.truncated ||
1319
- (display !== undefined &&
1320
- (display.stdoutDroppedBytes > 0 || display.stderrDroppedBytes > 0)),
1321
- toolPresentation: bounded.presentation
1328
+ toolPresentation: {
1329
+ kind: 'EXECUTION',
1330
+ execution: {
1331
+ toolName,
1332
+ ...executionMetadata,
1333
+ ...(truncation?.input ? { truncation: { input: true } } : {})
1334
+ }
1335
+ }
1322
1336
  },
1323
1337
  merge: true
1324
1338
  };
@@ -1421,8 +1435,8 @@ function execResult(value, summary, eventTruncated = false) {
1421
1435
  ...(exitCode === undefined ? {} : { exitCode }),
1422
1436
  ...(data?.cached === true || summaryData?.cached === true ? { cached: true } : {}),
1423
1437
  truncated: eventTruncated ||
1424
- data?.truncated === true ||
1425
- summaryData?.truncated === true ||
1438
+ (execDisplayOutput(data?.displayOutput) === undefined && data?.truncated === true) ||
1439
+ (data === undefined && summaryData?.truncated === true) ||
1426
1440
  structuredSummaryIncomplete
1427
1441
  };
1428
1442
  }
@@ -1458,14 +1472,11 @@ function marAgentSubagentProjection(toolName, input, output, knownTitle, knownDe
1458
1472
  ...(title.length === 0 ? {} : { title }),
1459
1473
  ...subagentPayload(details),
1460
1474
  ...(typeof details.latestMessage?.text === 'string'
1461
- ? {
1462
- outputSummary: boundedToolSummary(details.latestMessage.text),
1463
- output: publicOutputSnapshot(details.latestMessage.text, details.latestMessage.truncated === true)
1464
- }
1475
+ ? { outputSummary: null, output: undefined }
1465
1476
  : typeof outputData?.errorMessage === 'string'
1466
1477
  ? {
1467
- outputSummary: compactRunnerText(outputData.errorMessage, 10_000),
1468
- output: publicOutputSnapshot(outputData.errorMessage)
1478
+ outputSummary: null,
1479
+ output: publicToolOutput(outputData.errorMessage)
1469
1480
  }
1470
1481
  : {}),
1471
1482
  ...(typeof outputData?.errorCode === 'string'
@@ -1,3 +1,4 @@
1
+ import { boundToolOutput } from '@myagentroam/protocol';
1
2
  export function compactRunnerText(value, limit) {
2
3
  return value.length <= limit ? value : `${value.slice(0, Math.max(0, limit - 1))}…`;
3
4
  }
@@ -9,6 +10,10 @@ export function publicOutputSnapshot(value, truncated = false) {
9
10
  bounded = bounded.slice(1);
10
11
  return { text: bounded, truncated: truncated || text.length > 10_000 };
11
12
  }
13
+ export function publicToolOutput(value, truncation, channel = 'TEXT') {
14
+ const text = typeof value === 'string' ? value : compactRunnerValue(value, Number.MAX_SAFE_INTEGER).text;
15
+ return boundToolOutput(text ? [{ channel, text }] : [], truncation);
16
+ }
12
17
  export function compactRunnerValue(value, limit) {
13
18
  if (value === undefined || value === null)
14
19
  return { text: '', truncated: false };