@myagentroam/node 0.11.43 → 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.
@@ -75,30 +75,53 @@ export class NodeLifecycle {
75
75
  async stopResources(handoff) {
76
76
  let snapshot;
77
77
  let failure;
78
+ let stage = 'waitForRecovery';
79
+ const failed = (error) => {
80
+ failure ??= { error };
81
+ try {
82
+ this.shutdown.diagnose(stage, error);
83
+ }
84
+ catch {
85
+ // Diagnostics must not replace the shutdown failure or prevent cleanup.
86
+ }
87
+ };
78
88
  try {
79
89
  await this.shutdown.waitForRecovery();
90
+ stage = 'drainInput';
80
91
  await this.shutdown.drainInput();
81
92
  }
82
93
  catch (error) {
83
- failure = error;
94
+ failed(error);
84
95
  }
85
96
  try {
97
+ stage = 'closeResume';
86
98
  await this.shutdown.closeResume();
87
- if (failure === undefined && handoff)
99
+ if (failure === undefined && handoff) {
100
+ stage = 'capture';
88
101
  snapshot = this.shutdown.capture();
102
+ }
89
103
  }
90
104
  catch (error) {
91
- failure ??= error;
105
+ failed(error);
92
106
  }
93
107
  try {
108
+ stage = 'dispose';
94
109
  await this.shutdown.dispose();
95
110
  }
96
111
  catch (error) {
97
- failure ??= error;
112
+ failed(error);
98
113
  }
99
114
  if (failure !== undefined)
100
- throw failure;
101
- if (snapshot !== undefined)
102
- await this.shutdown.publish(snapshot);
115
+ throw failure.error;
116
+ if (snapshot !== undefined) {
117
+ try {
118
+ await this.shutdown.publish(snapshot);
119
+ }
120
+ catch (error) {
121
+ stage = 'publish';
122
+ failed(error);
123
+ throw error;
124
+ }
125
+ }
103
126
  }
104
127
  }
package/dist/connector.js CHANGED
@@ -197,7 +197,11 @@ export class NodeConnector {
197
197
  closeResume: () => this.externalSessionResumeService.close(),
198
198
  capture: () => (this.config?.nodeId ? this.restartHandoff.capture() : undefined),
199
199
  dispose: () => this.disposeRuntime(),
200
- publish: (snapshot) => this.restartHandoff.publish(snapshot)
200
+ publish: (snapshot) => this.restartHandoff.publish(snapshot),
201
+ diagnose: (stage, error) => nodeLog('node.shutdown.failed', {
202
+ stage,
203
+ errorCode: safeErrorCode(error, 'NODE_SHUTDOWN_FAILED')
204
+ })
201
205
  });
202
206
  get stopped() {
203
207
  return this.lifecycle.stopped;
package/dist/main.js CHANGED
@@ -45,10 +45,17 @@ if (command === 'run') {
45
45
  clearTimeout(deadline);
46
46
  releaseOwner();
47
47
  process.exit(exitCode);
48
- }, () => {
48
+ }, (error) => {
49
49
  clearTimeout(deadline);
50
- nodeLog('node.restart.diagnostic', { code: 'RESTART_HANDOFF_SHUTDOWN_FAILED' });
51
- process.exit(1);
50
+ try {
51
+ nodeLog('node.restart.diagnostic', {
52
+ code: 'RESTART_HANDOFF_SHUTDOWN_FAILED',
53
+ errorCode: safeErrorCode(error, 'NODE_SHUTDOWN_FAILED')
54
+ });
55
+ }
56
+ finally {
57
+ process.exit(1);
58
+ }
52
59
  });
53
60
  };
54
61
  const supervisorHandoff = new SupervisorUpgradeHandoff();
@@ -462,26 +462,19 @@ export class ClaudeManagedRunController {
462
462
  const tool = toolInputs.get(result.toolUseId);
463
463
  if (tool === undefined)
464
464
  continue;
465
- const normalized = normalizeClaudeTool(tool.name, tool.input, result.output, result.failed, tool.inputTruncated || result.truncated, knownSubagents, result.claudeResult, result.truncated);
465
+ const normalized = normalizeClaudeTool(tool.name, tool.input, result.output, result.failed, tool.inputTruncated || result.truncated, knownSubagents, result.claudeResult, result.truncated, tool.inputTruncated, result.truncated ? 'HEAD' : undefined);
466
466
  this.host.emitItem(runId, {
467
467
  itemId: boundedConversationItemId('claude-tool', result.toolUseId),
468
468
  kind,
469
469
  status: result.failed ? 'FAILED' : 'COMPLETED',
470
470
  payload: kind === 'command_execution'
471
- ? {
472
- outputPreview: result.output,
473
- exitCode: null,
474
- durationMs: null,
475
- truncated: result.truncated
476
- }
471
+ ? normalized.payload
477
472
  : kind === 'file_change'
478
473
  ? {}
479
474
  : {
480
475
  ...normalized.payload,
481
- outputSummary: result.output,
482
476
  progressLabel: null,
483
- errorCode: result.failed ? 'TOOL_EXECUTION_FAILED' : null,
484
- truncated: result.truncated
477
+ errorCode: result.failed ? 'TOOL_EXECUTION_FAILED' : null
485
478
  },
486
479
  merge: true
487
480
  });
@@ -339,7 +339,7 @@ export const claudeNativeTranscriptAdapter = {
339
339
  return {
340
340
  userText: (text) => publicGoalHistoryText(stripClaudePlanTag(text)),
341
341
  planText: claudePlanText,
342
- normalizeTool: (entry) => normalizeClaudeTool(entry.toolName, entry.input, entry.outputSummary, entry.failed, entry.inputTruncated || entry.outputTruncated, titles, claudeNativeToolResultMetadata(entry.subagentResult), entry.outputTruncated),
342
+ normalizeTool: (entry) => normalizeClaudeTool(entry.toolName, entry.input, entry.outputSummary, entry.failed, entry.inputTruncated || entry.outputTruncated, titles, claudeNativeToolResultMetadata(entry.subagentResult), entry.outputTruncated, entry.inputTruncated, entry.outputTruncated ? 'HEAD' : undefined),
343
343
  userInputQuestions: (entry) => entry.toolName === 'AskUserQuestion' && isPlainRecord(entry.input)
344
344
  ? claudeUserInputQuestions(entry.input)
345
345
  : []
@@ -1,5 +1,5 @@
1
1
  import { claudeContentBlocks } from './message-parsers.js';
2
- import { compactRunnerText, compactRunnerValue, publicOutputSnapshot } from '../normalization.js';
2
+ import { compactRunnerText, compactRunnerValue, publicOutputSnapshot, publicToolOutput } from '../normalization.js';
3
3
  import { shellExecution } from '../execution-presentation.js';
4
4
  import { RunnerUsageTracker, usageToken, usageTokenSum } from '../../service/runner-usage-tracker.js';
5
5
  export class ClaudeUsageNormalizer {
@@ -99,11 +99,9 @@ export function extractClaudeToolResults(message) {
99
99
  };
100
100
  });
101
101
  }
102
- export function normalizeClaudeTool(toolName, input, output, failed, truncated, knownSubagents = new Map(), resultMetadata, sourceOutputTruncated = output === null ? false : truncated) {
102
+ export function normalizeClaudeTool(toolName, input, output, failed, truncated, knownSubagents = new Map(), resultMetadata, sourceOutputTruncated = output === null ? false : truncated, sourceInputTruncated = output === null ? truncated : false, outputDirection) {
103
103
  const values = record(input);
104
104
  const normalizedToolName = compactRunnerText(toolName, 128) || 'unknown';
105
- const normalizedOutput = output === null ? null : compactRunnerText(output, 10_000);
106
- const outputTruncated = sourceOutputTruncated || (output?.length ?? 0) > 10_000;
107
105
  const subagent = claudeSubagentProjection(toolName, input, resultMetadata ?? output, knownSubagents);
108
106
  if (isCommandTool(toolName))
109
107
  return {
@@ -112,14 +110,15 @@ export function normalizeClaudeTool(toolName, input, output, failed, truncated,
112
110
  toolName: normalizedToolName,
113
111
  command: compactRunnerText(typeof values?.command === 'string' ? values.command : normalizedToolName, 10_000),
114
112
  cwd: null,
115
- outputPreview: normalizedOutput ?? '',
116
113
  exitCode: null,
117
114
  durationMs: null,
118
- truncated: truncated || outputTruncated,
119
115
  ...(output === null || output === undefined
120
116
  ? {}
121
- : { output: publicOutputSnapshot(output, outputTruncated) }),
122
- toolPresentation: shellExecution(output ?? undefined, outputTruncated)
117
+ : {
118
+ output: publicToolOutput(output, outputDirection ?? (sourceOutputTruncated ? 'SOURCE' : undefined), 'COMBINED')
119
+ }),
120
+ toolPresentation: shellExecution(sourceInputTruncated ||
121
+ (typeof values?.command === 'string' && values.command.length > 10_000))
123
122
  }
124
123
  };
125
124
  if (isFileTool(toolName)) {
@@ -154,11 +153,17 @@ export function normalizeClaudeTool(toolName, input, output, failed, truncated,
154
153
  toolName: normalizedToolName,
155
154
  title: compactRunnerText(toolName, 500) || normalizedToolName,
156
155
  inputSummary: compactRunnerValue(input, 10_000).text || null,
157
- outputSummary: normalizedOutput,
158
- ...(output === null ? {} : { output: publicOutputSnapshot(output, outputTruncated) }),
156
+ outputSummary: null,
157
+ ...(output === null
158
+ ? {}
159
+ : {
160
+ output: publicToolOutput(output, outputDirection ?? (sourceOutputTruncated ? 'SOURCE' : undefined))
161
+ }),
159
162
  progressLabel: null,
160
163
  errorCode: failed ? 'TOOL_EXECUTION_FAILED' : null,
161
- truncated: truncated || outputTruncated,
164
+ ...(sourceInputTruncated || compactRunnerValue(input, 10_000).truncated
165
+ ? { inputTruncated: true }
166
+ : {}),
162
167
  ...(toolName === 'Read'
163
168
  ? {
164
169
  toolPresentation: {
@@ -132,7 +132,7 @@ import { conversationImageAttachmentSchema } from '@myagentroam/protocol';
132
132
  import { parseCodexComposerImageMarkers } from './composer-image-markers.js';
133
133
  import { normalizeCodexTool } from './tool-normalizer.js';
134
134
  import { shellExecution } from '../execution-presentation.js';
135
- import { publicOutputSnapshot } from '../normalization.js';
135
+ import { publicToolOutput } from '../normalization.js';
136
136
  import { publicGoalHistoryText } from '../../service/goal-runtime.js';
137
137
  const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
138
138
  const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
@@ -551,13 +551,9 @@ export function mergeCodexCustomToolItems(invocation, result) {
551
551
  completedAt: result.completedAt,
552
552
  payload: {
553
553
  ...invocationPayload,
554
- outputPreview: resultPayload.outputPreview ??
555
- resultPayload.outputSummary ??
556
- invocationPayload.outputPreview,
557
554
  ...(resultPayload.output === undefined ? {} : { output: resultPayload.output }),
558
555
  exitCode: resultPayload.exitCode ?? invocationPayload.exitCode,
559
556
  durationMs: resultPayload.durationMs ?? invocationPayload.durationMs,
560
- truncated: invocationPayload.truncated === true || resultPayload.truncated === true,
561
557
  ...(resultPayload.attachments === undefined
562
558
  ? {}
563
559
  : { attachments: resultPayload.attachments })
@@ -576,7 +572,6 @@ export function mergeCodexCustomToolItems(invocation, result) {
576
572
  outputSummary: resultPayload.outputSummary ?? invocationPayload.outputSummary,
577
573
  ...(resultPayload.output === undefined ? {} : { output: resultPayload.output }),
578
574
  errorCode: resultPayload.errorCode ?? invocationPayload.errorCode,
579
- truncated: invocationPayload.truncated === true || resultPayload.truncated === true,
580
575
  ...(resultPayload.attachments === undefined ? {} : { attachments: resultPayload.attachments })
581
576
  }
582
577
  };
@@ -720,7 +715,6 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
720
715
  ? compactRunnerText(value.command, 10_000)
721
716
  : '(No command provided)',
722
717
  cwd: typeof value.cwd === 'string' ? compactRunnerText(value.cwd, 4_096) : null,
723
- outputPreview: publicOutputSnapshot(output).text,
724
718
  exitCode: typeof value.exitCode === 'number' && Number.isInteger(value.exitCode)
725
719
  ? value.exitCode
726
720
  : null,
@@ -729,21 +723,16 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
729
723
  value.durationMs >= 0
730
724
  ? value.durationMs
731
725
  : null,
732
- truncated: output.length > 10_000,
733
- ...(base.status !== 'IN_PROGRESS'
734
- ? {
735
- output: typeof value.aggregatedOutput === 'string'
736
- ? publicOutputSnapshot(output)
737
- : undefined
738
- }
726
+ ...(typeof value.aggregatedOutput === 'string'
727
+ ? { output: publicToolOutput(output, undefined, 'COMBINED') }
739
728
  : {}),
740
- toolPresentation: shellExecution(base.status === 'IN_PROGRESS' || typeof value.aggregatedOutput !== 'string'
741
- ? undefined
742
- : output)
729
+ toolPresentation: shellExecution((typeof value.command === 'string' && value.command.length > 10_000) ||
730
+ (typeof value.cwd === 'string' && value.cwd.length > 4_096))
743
731
  }
744
732
  };
745
733
  }
746
- if (value.type === 'mcpToolCall')
734
+ if (value.type === 'mcpToolCall') {
735
+ const input = compactRunnerValue(value.arguments, 10_000);
747
736
  return {
748
737
  ...base,
749
738
  kind: 'tool_call',
@@ -756,12 +745,14 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
756
745
  .join('.'),
757
746
  namespace: typeof value.server === 'string' ? value.server : '',
758
747
  toolName: typeof value.tool === 'string' ? value.tool : '',
759
- inputSummary: codexCompactJson(value.arguments),
760
- outputSummary: codexCompactJson(value.result),
761
- ...(value.result == null ? {} : { output: publicOutputSnapshot(value.result) }),
748
+ inputSummary: input.text,
749
+ ...(input.truncated ? { inputTruncated: true } : {}),
750
+ outputSummary: null,
751
+ ...(value.result == null ? {} : { output: publicToolOutput(value.result) }),
762
752
  ...(value.error === undefined ? {} : { errorCode: codexCompactJson(value.error) })
763
753
  }
764
754
  };
755
+ }
765
756
  if (value.type === 'webSearch')
766
757
  return {
767
758
  ...base,
@@ -804,15 +795,16 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
804
795
  ? value.name
805
796
  : 'tool';
806
797
  const outputValue = value.contentItems ?? value.output;
807
- const output = outputValue == null ? undefined : publicOutputSnapshot(codexToolOutput(outputValue));
798
+ const output = outputValue == null ? undefined : codexToolOutput(outputValue);
799
+ const input = compactRunnerValue(value.arguments ?? value.input, 10_000);
808
800
  const normalized = normalizeCodexTool({
809
801
  name,
810
802
  namespace: typeof value.namespace === 'string' ? value.namespace : 'codex',
811
803
  rawInput: value.arguments ?? value.input,
812
- inputSummary: codexCompactJson(value.arguments ?? value.input),
813
- outputSummary: output?.text ?? null,
814
- outputTruncated: output?.truncated ?? false,
815
- truncated: output?.truncated ?? false,
804
+ inputSummary: input.text || null,
805
+ inputTruncated: input.truncated,
806
+ outputSummary: output === undefined ? null : typeof output === 'string' ? output : JSON.stringify(output),
807
+ outputTruncated: false,
816
808
  outputOnly: value.type === 'custom_tool_call_output' ||
817
809
  value.type === 'function_call_output' ||
818
810
  value.type === 'customToolCallOutput' ||
@@ -906,7 +898,6 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
906
898
  ? compactRunnerText(value.command, 10_000)
907
899
  : '(No command provided)',
908
900
  cwd: typeof value.cwd === 'string' ? compactRunnerText(value.cwd, 4_096) : null,
909
- outputPreview: publicOutputSnapshot(output).text,
910
901
  exitCode: typeof value.exitCode === 'number' && Number.isInteger(value.exitCode)
911
902
  ? value.exitCode
912
903
  : null,
@@ -915,21 +906,20 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
915
906
  value.durationMs >= 0
916
907
  ? value.durationMs
917
908
  : null,
918
- truncated: output.length > 10_000,
919
909
  ...(completed
920
910
  ? {
921
911
  output: typeof value.aggregatedOutput === 'string'
922
- ? publicOutputSnapshot(output)
912
+ ? publicToolOutput(output, undefined, 'COMBINED')
923
913
  : undefined
924
914
  }
925
915
  : {}),
926
- toolPresentation: shellExecution(!completed || typeof value.aggregatedOutput !== 'string' ? undefined : output)
916
+ toolPresentation: shellExecution((typeof value.command === 'string' && value.command.length > 10_000) ||
917
+ (typeof value.cwd === 'string' && value.cwd.length > 4_096))
927
918
  }
928
919
  };
929
920
  }
930
921
  if (value.type === 'mcpToolCall') {
931
922
  const input = compactRunnerValue(value.arguments, 10_000);
932
- const output = compactRunnerValue(value.result, 10_000);
933
923
  const error = compactRunnerValue(value.error, 80);
934
924
  const server = typeof value.server === 'string' ? value.server : 'mcp';
935
925
  const tool = typeof value.tool === 'string' ? value.tool : 'tool';
@@ -942,11 +932,11 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
942
932
  toolName: compactRunnerText(tool, 128),
943
933
  title: compactRunnerText(`${server}.${tool}`, 500),
944
934
  inputSummary: input.text || null,
945
- outputSummary: output.text || null,
946
- ...(value.result == null ? {} : { output: publicOutputSnapshot(value.result) }),
935
+ outputSummary: null,
936
+ ...(value.result == null ? {} : { output: publicToolOutput(value.result) }),
947
937
  progressLabel: null,
948
938
  errorCode: error.text || null,
949
- truncated: input.truncated || output.truncated || error.truncated
939
+ ...(input.truncated ? { inputTruncated: true } : {})
950
940
  }
951
941
  };
952
942
  }
@@ -1021,7 +1011,7 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
1021
1011
  : 'tool';
1022
1012
  const input = compactRunnerValue(value.arguments ?? value.input, 10_000);
1023
1013
  const outputValue = value.contentItems ?? value.output;
1024
- const output = outputValue == null ? undefined : publicOutputSnapshot(codexToolOutput(outputValue));
1014
+ const output = outputValue == null ? undefined : codexToolOutput(outputValue);
1025
1015
  const outputOnly = value.type === 'custom_tool_call_output' ||
1026
1016
  value.type === 'function_call_output' ||
1027
1017
  value.type === 'customToolCallOutput' ||
@@ -1031,9 +1021,10 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
1031
1021
  namespace: typeof value.namespace === 'string' ? value.namespace : 'codex',
1032
1022
  rawInput: value.arguments ?? value.input,
1033
1023
  inputSummary: input.text || null,
1034
- outputSummary: output?.text ?? null,
1035
- truncated: input.truncated || output?.truncated === true,
1036
- outputTruncated: output?.truncated ?? false,
1024
+ inputTruncated: input.truncated,
1025
+ outputSummary: output === undefined ? null : typeof output === 'string' ? output : JSON.stringify(output),
1026
+ truncated: input.truncated,
1027
+ outputTruncated: false,
1037
1028
  outputOnly,
1038
1029
  attachments: imageAttachments
1039
1030
  });
@@ -1,7 +1,7 @@
1
1
  import { NODE_MCP } from '../../mcp/naming.js';
2
2
  import { nodeExecutionEnvironment } from '../../node-execution-environment.js';
3
3
  import { extractId } from './conversation-parser.js';
4
- import { publicOutputSnapshot } from '../normalization.js';
4
+ import { publicToolOutput } from '../normalization.js';
5
5
  import { normalizeCodexApproval } from './tool-normalizer.js';
6
6
  import { parseSecretEnvironment } from '../../mcp/secret-environment.js';
7
7
  import { safeErrorCode } from '../../util/safe-error.js';
@@ -280,14 +280,14 @@ export class CodexManagedRunController {
280
280
  if (notification.method === 'item/commandExecution/outputDelta' &&
281
281
  typeof params.itemId === 'string' &&
282
282
  typeof params.delta === 'string') {
283
- const output = publicOutputSnapshot(params.delta);
283
+ const output = publicToolOutput(params.delta, undefined, 'COMBINED');
284
284
  this.emitItem(runId, {
285
285
  itemId: boundedConversationItemId('codex', params.itemId),
286
286
  kind: 'command_execution',
287
287
  status: 'IN_PROGRESS',
288
- payload: { outputPreview: output.text, output },
288
+ payload: { output },
289
289
  merge: true,
290
- append: 'output'
290
+ ...(output.truncation === 'HEAD' ? {} : { append: 'output' })
291
291
  });
292
292
  return;
293
293
  }
@@ -1,7 +1,7 @@
1
1
  import { homedir } from 'node:os';
2
2
  import { basename, join } from 'node:path';
3
3
  import { forEachJsonlRecord } from '../../native-jsonl-reader.js';
4
- import { compactRunnerValue as compactValue } from '../normalization.js';
4
+ import { compactRunnerValue as compactValue, publicOutputSnapshot } from '../normalization.js';
5
5
  import { stripGoalStartPrompt, publicGoalHistoryText } from '../../service/goal-runtime.js';
6
6
  import { parseCodexComposerImageMarkers } from './composer-image-markers.js';
7
7
  import { codexTranscriptSourceItemId } from './conversation-parser.js';
@@ -167,7 +167,7 @@ function codexToolResultsOf(record, createdAt) {
167
167
  const toolUseId = firstString(payload, ['call_id', 'id']);
168
168
  if (toolUseId === undefined)
169
169
  return [];
170
- const output = compactValue(codexToolOutput(payload['output'] ?? payload['contentItems']), 10_000);
170
+ const output = publicOutputSnapshot(codexToolOutput(payload['output'] ?? payload['contentItems']));
171
171
  const imageAttachments = codexImagePlaceholders(payload['output'] ?? payload['contentItems']);
172
172
  return [
173
173
  {
@@ -566,7 +566,9 @@ export const codexNativeTranscriptAdapter = {
566
566
  rawInput: entry.input,
567
567
  inputSummary: entry.inputSummary,
568
568
  outputSummary: entry.outputSummary,
569
- truncated: entry.inputTruncated || entry.outputTruncated,
569
+ inputTruncated: entry.inputTruncated,
570
+ outputTruncated: entry.outputTruncated,
571
+ ...(entry.outputTruncated ? { outputDirection: 'HEAD' } : {}),
570
572
  ...(entry.imageAttachments === undefined ? {} : { attachments: entry.imageAttachments })
571
573
  }),
572
574
  userInputQuestions: () => []
@@ -1,4 +1,4 @@
1
- import { compactRunnerText, publicOutputSnapshot } from '../normalization.js';
1
+ import { compactRunnerText, publicToolOutput } from '../normalization.js';
2
2
  import { shellExecution } from '../execution-presentation.js';
3
3
  export function normalizeCodexApproval(method) {
4
4
  return method === 'item/commandExecution/requestApproval'
@@ -10,20 +10,18 @@ export function normalizeCodexTool(input) {
10
10
  const rawInput = input.rawInput;
11
11
  const attachments = input.attachments ?? [];
12
12
  const outputTruncated = input.outputTruncated ?? (input.outputSummary != null && input.truncated === true);
13
+ const outputDirection = input.outputDirection ?? (outputTruncated ? 'SOURCE' : undefined);
13
14
  const subagent = codexSubagentProjection(input.namespace, name, rawInput, input.outputSummary);
14
15
  if (isCommandTool(name)) {
15
- const outputPreview = compactRunnerText(input.outputSummary ?? '', 10_000);
16
16
  if (input.outputOnly === true)
17
17
  return {
18
18
  kind: 'command_execution',
19
19
  payload: {
20
20
  toolName: name,
21
- outputPreview,
22
- truncated: input.truncated === true || (input.outputSummary?.length ?? 0) > 10_000,
23
21
  ...(input.outputSummary == null
24
22
  ? {}
25
- : { output: publicOutputSnapshot(input.outputSummary, outputTruncated) }),
26
- toolPresentation: shellExecution(input.outputSummary ?? undefined, outputTruncated),
23
+ : { output: publicToolOutput(input.outputSummary, outputDirection, 'COMBINED') }),
24
+ toolPresentation: shellExecution(),
27
25
  ...(attachments.length === 0 ? {} : { attachments })
28
26
  }
29
27
  };
@@ -41,16 +39,14 @@ export function normalizeCodexTool(input) {
41
39
  toolName: name,
42
40
  command: compactRunnerText(command, 10_000),
43
41
  cwd: typeof values?.cwd === 'string' ? compactRunnerText(values.cwd, 4_096) : null,
44
- outputPreview,
45
42
  exitCode: null,
46
43
  durationMs: null,
47
- toolPresentation: shellExecution(input.outputSummary ?? undefined, outputTruncated),
44
+ toolPresentation: shellExecution(input.inputTruncated === true ||
45
+ command.length > 10_000 ||
46
+ (typeof values?.cwd === 'string' && values.cwd.length > 4_096)),
48
47
  ...(input.outputSummary == null
49
48
  ? {}
50
- : { output: publicOutputSnapshot(input.outputSummary, outputTruncated) }),
51
- truncated: input.truncated === true ||
52
- command.length > 10_000 ||
53
- (input.outputSummary?.length ?? 0) > 10_000,
49
+ : { output: publicToolOutput(input.outputSummary, outputDirection, 'COMBINED') }),
54
50
  ...(attachments.length === 0 ? {} : { attachments })
55
51
  }
56
52
  };
@@ -59,10 +55,10 @@ export function normalizeCodexTool(input) {
59
55
  return {
60
56
  kind: 'tool_call',
61
57
  payload: {
62
- outputSummary: input.outputSummary == null ? null : compactRunnerText(input.outputSummary, 10_000),
58
+ outputSummary: null,
63
59
  ...(input.outputSummary == null
64
60
  ? {}
65
- : { output: publicOutputSnapshot(input.outputSummary, outputTruncated) }),
61
+ : { output: publicToolOutput(input.outputSummary, outputDirection) }),
66
62
  ...subagent,
67
63
  ...(attachments.length === 0 ? {} : { attachments })
68
64
  }
@@ -74,13 +70,13 @@ export function normalizeCodexTool(input) {
74
70
  namespace: compactRunnerText(input.namespace ?? 'codex', 128),
75
71
  toolName: name,
76
72
  inputSummary: input.inputSummary ?? null,
77
- outputSummary: input.outputSummary == null ? null : compactRunnerText(input.outputSummary, 10_000),
73
+ outputSummary: null,
78
74
  ...(input.outputSummary == null
79
75
  ? {}
80
- : { output: publicOutputSnapshot(input.outputSummary, outputTruncated) }),
76
+ : { output: publicToolOutput(input.outputSummary, outputDirection) }),
81
77
  progressLabel: null,
82
78
  errorCode: null,
83
- truncated: input.truncated === true,
79
+ ...(input.inputTruncated ? { inputTruncated: true } : {}),
84
80
  ...subagent,
85
81
  ...(attachments.length === 0 ? {} : { attachments })
86
82
  }
@@ -1,53 +1,10 @@
1
- const MAX_BYTES = 128 * 1024;
2
- function tail(text, limit) {
3
- if (limit === 0)
4
- return '';
5
- const result = text.slice(-limit);
6
- const first = result.charCodeAt(0);
7
- return first >= 0xdc00 && first <= 0xdfff ? result.slice(1) : result;
8
- }
9
- /** Keep public execution output bounded without copying an unbounded source. */
10
- export function boundExecutionPresentation(execution) {
11
- let truncated = (execution.output?.length ?? 0) > 4;
12
- const output = execution.output?.slice(0, 4).map((block) => {
13
- if (block.text.length > 10_000)
14
- truncated = true;
15
- return { ...block, text: tail(block.text, 10_000) };
16
- });
17
- const detail = {
18
- ...execution,
19
- ...(output === undefined ? {} : { output }),
20
- ...(execution.truncation === undefined ? {} : { truncation: { ...execution.truncation } })
21
- };
22
- if (truncated)
23
- detail.truncation = { ...detail.truncation, output: detail.truncation?.output ?? 'HEAD' };
24
- const presentation = { kind: 'EXECUTION', execution: detail };
25
- const size = () => Buffer.byteLength(JSON.stringify(presentation));
26
- for (const block of [...(detail.output ?? [])].reverse()) {
27
- if (size() <= MAX_BYTES)
28
- break;
29
- const source = block.text;
30
- detail.truncation = { ...detail.truncation, output: detail.truncation?.output ?? 'HEAD' };
31
- truncated = true;
32
- let low = 0;
33
- let high = source.length;
34
- while (low < high) {
35
- const middle = Math.ceil((low + high) / 2);
36
- block.text = tail(source, middle);
37
- if (size() <= MAX_BYTES)
38
- low = middle;
39
- else
40
- high = middle - 1;
1
+ /** Execution metadata is independent of the public output body and its budget. */
2
+ export function shellExecution(inputTruncated = false) {
3
+ return {
4
+ kind: 'EXECUTION',
5
+ execution: {
6
+ kind: 'SHELL',
7
+ ...(inputTruncated ? { truncation: { input: true } } : {})
41
8
  }
42
- block.text = tail(source, low);
43
- }
44
- return { presentation, truncated };
45
- }
46
- /** Project only the stream facts actually supplied by the runner. */
47
- export function shellExecution(output, sourceTruncated = false) {
48
- return boundExecutionPresentation({
49
- kind: 'SHELL',
50
- ...(output === undefined ? {} : { output: [{ channel: 'COMBINED', text: output }] }),
51
- ...(sourceTruncated ? { truncation: { output: 'SOURCE' } } : {})
52
- }).presentation;
9
+ };
53
10
  }