@myagentroam/agent 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.
@@ -7,6 +7,13 @@ import { registerQueryTools } from './query.js';
7
7
  import { nativeSubagentService, SUBAGENT_NATIVE_SERVICE } from './native.js';
8
8
  import { childResources } from './resources.js';
9
9
  import { sharedSessionHistory } from './history.js';
10
+ const delegationPrompt = [
11
+ '# Delegation and coordination',
12
+ 'Delegate bounded, self-contained tasks when independent progress or context savings materially helps. Keep simple or tightly coupled work local.',
13
+ 'Give each child an objective, necessary context, expected output, and a read-only boundary or non-overlapping write scope.',
14
+ 'While children run, continue meaningful non-overlapping work. Do not redo delegated work. Wait only when the next step needs the result.',
15
+ 'Verify consequential findings and changes. Join or stop task-required child work before final delivery, and retain responsibility for the final result.'
16
+ ].join('\n\n');
10
17
  export { SUBAGENT_NATIVE_SERVICE } from './native.js';
11
18
  /** Optional local consumer of Core Session lifecycle and observation APIs. */
12
19
  export function createSubagentExtension(options) {
@@ -70,6 +77,18 @@ export function createSubagentExtension(options) {
70
77
  registerControlTools(context, agent, observations, warmObservations, admission);
71
78
  registerWait(context, agent, observations, history);
72
79
  registerQueryTools(context, agent);
80
+ context.hooks.intercept('prompt.sections', (input, _call, next) => next({
81
+ ...input,
82
+ sections: [
83
+ ...input.sections,
84
+ {
85
+ id: 'subagent:delegation',
86
+ text: input.facts?.mode !== 'compact' && input.facts?.tools?.includes('agent_start')
87
+ ? delegationPrompt
88
+ : null
89
+ }
90
+ ]
91
+ }));
73
92
  }
74
93
  catch (error) {
75
94
  disposed = true;
@@ -81,6 +81,7 @@ export function publicHostedToolEvents(event) {
81
81
  callId: event.itemId,
82
82
  summary,
83
83
  truncated,
84
+ ...(preview.truncated || event.truncated === true ? { summaryTruncated: true } : {}),
84
85
  data: started.data
85
86
  }
86
87
  ];
@@ -95,6 +95,7 @@ export type AgentEvent = AgentEventBase & ({
95
95
  callId: string;
96
96
  parentCallId?: string;
97
97
  output: import('../tools/contracts.js').ExecOutputSnapshot;
98
+ outputTruncation?: 'HEAD' | 'SOURCE';
98
99
  } | {
99
100
  type: 'tool.completed';
100
101
  resultExtensions?: readonly import('../tools/result-extension-contract.js').ToolResultExtension[];
@@ -103,6 +104,8 @@ export type AgentEvent = AgentEventBase & ({
103
104
  parentCallId?: string;
104
105
  summary: string;
105
106
  truncated?: boolean;
107
+ summaryTruncated?: boolean;
108
+ outputTruncation?: 'HEAD' | 'SOURCE';
106
109
  artifacts?: readonly AgentToolArtifact[];
107
110
  data?: CodeCellEventData | import('../tools/contracts.js').ExecResult | Omit<import('../tools/contracts.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/contracts.js').ApplyPatchResult | import('../tools/contracts.js').ReadEventData | import('../tools/contracts.js').ViewImageResult | import('../tools/contracts.js').SleepResult | Readonly<Record<string, unknown>> | import('./public-tool-metadata.js').PublicToolMetadata;
108
111
  } | {
@@ -116,6 +119,7 @@ export type AgentEvent = AgentEventBase & ({
116
119
  message?: string;
117
120
  permissionStop?: import('../tools/permission-contract.js').PermissionStop;
118
121
  truncated?: boolean;
122
+ outputTruncation?: 'HEAD' | 'SOURCE';
119
123
  } | {
120
124
  type: 'sleep.started';
121
125
  callId: string;
@@ -241,6 +241,13 @@ export class SessionExecution {
241
241
  const deliver = missed && full.output.displayOutput
242
242
  ? {
243
243
  ...full,
244
+ ...(full.outputTruncation === 'SOURCE' ||
245
+ full.output.displayOutput.stdoutDroppedBytes > 0 ||
246
+ full.output.displayOutput.stderrDroppedBytes > 0
247
+ ? {
248
+ outputTruncation: full.outputTruncation === 'SOURCE' ? 'SOURCE' : 'HEAD'
249
+ }
250
+ : {}),
244
251
  output: {
245
252
  ...full.output,
246
253
  mode: 'snapshot',
@@ -3,7 +3,7 @@ import type { ToolExecutionContext, ToolExecutionResult } from '../contracts.js'
3
3
  import type { CodeObservation, CodeToolCallRecord } from '../../session/code-observation-contract.js';
4
4
  export interface CellOutput {
5
5
  call(value: CodeToolCallRecord): void;
6
- observe(value: CodeObservation): void;
6
+ observe(value: CodeObservation, display?: CodeObservation, displayHeadTruncated?: boolean): void;
7
7
  text(value: string, originalBytes?: number): void;
8
8
  image(value: NonNullable<ToolExecutionResult['images']>[number]): void;
9
9
  yield(): void;
@@ -6,6 +6,7 @@ import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
6
6
  import { waitForToolCondition, waitMetadata } from '../wait.js';
7
7
  import { BoundedTextOutput, renderCapturedText } from './text-output.js';
8
8
  import { boundObservations, newObservationId } from './observations.js';
9
+ import { publicCodeObservations } from '../output-projection.js';
9
10
  import { selectObservationImages, visibleObservation } from '../../session/code-observation-projection.js';
10
11
  /** Keeps a failed tool call observable without discarding earlier explicit output. */
11
12
  export class CodeCellExecutionError extends AgentError {
@@ -214,7 +215,26 @@ export class CodeCells {
214
215
  releaseSession = await context.codeMode?.retainSession?.();
215
216
  return run(source, { ...context, signal: controller.signal, waitInterrupt: undefined }, {
216
217
  call: (value) => cell.calls.set(value.callId, value),
217
- observe: (value) => cell.observations.push(value),
218
+ observe: (value, display = value, displayHeadTruncated = false) => {
219
+ cell.observations.push(value);
220
+ const observations = [...(cell.display?.observations ?? []), display];
221
+ cell.display = publicCodeObservations(observations, {
222
+ cellId: cell.cellId,
223
+ status: cell.status,
224
+ observations,
225
+ outputSourceKnown: true,
226
+ ...(cell.display?.outputSourceTruncated ||
227
+ (!displayHeadTruncated &&
228
+ (display.type === 'value'
229
+ ? display.truncated === true
230
+ : display.result.truncated === true))
231
+ ? { outputSourceTruncated: true }
232
+ : {}),
233
+ ...(cell.display?.outputHeadTruncated || displayHeadTruncated
234
+ ? { outputHeadTruncated: true }
235
+ : {})
236
+ });
237
+ },
218
238
  text: (text, originalBytes) => cell.text.append(text, originalBytes),
219
239
  image: (image) => cell.images.push(image),
220
240
  yield: () => {
@@ -359,7 +379,14 @@ export class CodeCells {
359
379
  observations: visible.observations.map(visibleObservation)
360
380
  }),
361
381
  data,
362
- eventData: { ...data, executionSource: cell.source },
382
+ eventData: {
383
+ ...data,
384
+ ...cell.display,
385
+ status: cell.status,
386
+ cached: true,
387
+ ...wait,
388
+ executionSource: cell.source
389
+ },
363
390
  truncated: data.truncated
364
391
  };
365
392
  }
@@ -430,7 +457,12 @@ export class CodeCells {
430
457
  calls: [...cell.calls.values()],
431
458
  observations: remapped,
432
459
  data,
433
- eventData: { ...data, executionSource: cell.source },
460
+ eventData: {
461
+ ...data,
462
+ ...cell.display,
463
+ status: cell.status,
464
+ executionSource: cell.source
465
+ },
434
466
  truncated: cell.truncated || structured.truncated,
435
467
  ...(visibleImages.length ? { images: visibleImages } : {})
436
468
  };
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { AgentError, describeAgentError } from '../../error.js';
3
3
  import { toolEventSummary } from '../../runtime/event-projection.js';
4
4
  import { allowsToolCaller } from '../callers.js';
5
- import { boundedModelToolOutput, structuredToolEventData, structuredToolFailureData } from '../output-projection.js';
5
+ import { boundedModelToolOutput, publicToolEventInput, publicExtensionToolEventData, publicEventOutputData, publicOutputTruncation, structuredToolEventData, structuredToolFailureData } from '../output-projection.js';
6
6
  import { executeRegisteredTool } from '../runtime.js';
7
7
  import { failedToolResultExtensions } from '../result-extensions.js';
8
8
  import { permissionStopFromError } from '../permissions.js';
@@ -19,7 +19,13 @@ export function createCodeToolDispatch(input) {
19
19
  signal.throwIfAborted();
20
20
  const callId = call.callId ?? randomUUID();
21
21
  const identity = { toolName: call.name, callId, parentCallId: input.parentCallId };
22
- await input.emit({ type: 'tool.started', ...identity, input: call.arguments });
22
+ await input.emit({
23
+ type: 'tool.started',
24
+ ...identity,
25
+ input: definition.eventInputFields === undefined
26
+ ? call.arguments
27
+ : publicToolEventInput(call.arguments, definition.eventInputFields)
28
+ });
23
29
  try {
24
30
  input.assertLive();
25
31
  signal.throwIfAborted();
@@ -32,23 +38,35 @@ export function createCodeToolDispatch(input) {
32
38
  ...(expected ? { expectedMcpTool: expected } : {})
33
39
  }, 'programmatic');
34
40
  const bounded = boundedModelToolOutput(call.name, result.content, result.data);
35
- const data = structuredToolEventData(call.name, call.name === 'exec' || call.name === 'exec_wait'
36
- ? (result.eventData ?? result.data)
37
- : result.data);
38
- const preview = toolEventSummary(result.content);
41
+ const data = call.name === 'exec' || call.name === 'exec_wait'
42
+ ? structuredToolEventData(call.name, result.eventData ?? result.data)
43
+ : result.eventData === undefined
44
+ ? structuredToolEventData(call.name, result.data)
45
+ : publicExtensionToolEventData(result.eventData);
46
+ const preview = toolEventSummary(result.eventSummary === undefined
47
+ ? result.content
48
+ : typeof result.eventSummary === 'string'
49
+ ? result.eventSummary
50
+ : '');
51
+ const outputTruncation = publicOutputTruncation(call.name, data);
39
52
  input.assertLive();
40
53
  await input.emit({
41
54
  type: 'tool.completed',
42
55
  ...(result.resultExtensions ? { resultExtensions: result.resultExtensions } : {}),
43
56
  ...identity,
44
57
  summary: preview.summary,
45
- ...(data === undefined ? {} : { data }),
58
+ ...(preview.truncated ? { summaryTruncated: true } : {}),
59
+ ...(outputTruncation ? { outputTruncation } : {}),
60
+ ...(data === undefined ? {} : { data: publicEventOutputData(data) }),
46
61
  truncated: result.truncated === true || bounded.truncated || preview.truncated,
47
62
  ...(result.artifacts ? { artifacts: result.artifacts } : {})
48
63
  });
49
64
  const { eventData: _eventData, ...guestResult } = result;
50
65
  return {
51
66
  ...guestResult,
67
+ ...((call.name === 'exec' || call.name === 'exec_wait') && data !== undefined
68
+ ? { eventData: { ...data } }
69
+ : {}),
52
70
  content: bounded.content,
53
71
  truncated: result.truncated === true || bounded.truncated
54
72
  };
@@ -41,6 +41,8 @@ export interface CodeModeEngine {
41
41
  format: 'text' | 'json';
42
42
  value: string;
43
43
  originalBytes: number;
44
+ /** Public display tail, independent of the model capture budget. */
45
+ displayValue?: string;
44
46
  } | {
45
47
  type: 'image';
46
48
  value: unknown;
@@ -300,7 +300,7 @@ export class CodeModeTool {
300
300
  const images = [];
301
301
  const observations = [];
302
302
  let observationBytes = 0;
303
- const appendObservation = (value, originalBytes) => {
303
+ const appendObservation = (value, originalBytes, display) => {
304
304
  const capture = new BoundedTextOutput(Math.max(0, limits.codeMaxOutputBytes - observationBytes), '');
305
305
  const body = value.type === 'tool_result' ? value.result.content : value.value;
306
306
  capture.append(body);
@@ -323,7 +323,10 @@ export class CodeModeTool {
323
323
  truncated: value.truncated === true || bounded.droppedBytes > 0
324
324
  };
325
325
  observations.push(observation);
326
- output?.observe(observation);
326
+ output?.observe(observation, display ?? value, display !== undefined &&
327
+ (display.type === 'tool_result'
328
+ ? display.result.truncated === true
329
+ : display.truncated === true));
327
330
  };
328
331
  const toolResults = new Map();
329
332
  let transportBytes = 0;
@@ -389,6 +392,7 @@ export class CodeModeTool {
389
392
  return index;
390
393
  };
391
394
  let observation;
395
+ let displayObservation;
392
396
  if (value.type === 'tool_result' && typeof value.operationId === 'string') {
393
397
  const original = toolResults.get(value.operationId);
394
398
  if (!original)
@@ -423,6 +427,23 @@ export class CodeModeTool {
423
427
  ...(indices?.length ? { imageIndices: indices } : {})
424
428
  }
425
429
  };
430
+ const display = original.result.eventData?.displayOutput;
431
+ if ((original.source.toolName === 'exec' || original.source.toolName === 'exec_wait') &&
432
+ display !== null &&
433
+ typeof display === 'object' &&
434
+ 'stdout' in display &&
435
+ 'stderr' in display &&
436
+ typeof display.stdout === 'string' &&
437
+ typeof display.stderr === 'string')
438
+ displayObservation = {
439
+ ...observation,
440
+ result: {
441
+ content: '',
442
+ data: { stdout: display.stdout, stderr: display.stderr },
443
+ truncated: ('stdoutDroppedBytes' in display && Number(display.stdoutDroppedBytes) > 0) ||
444
+ ('stderrDroppedBytes' in display && Number(display.stderrDroppedBytes) > 0)
445
+ }
446
+ };
426
447
  }
427
448
  else if (value.type === 'image') {
428
449
  observation = {
@@ -444,12 +465,22 @@ export class CodeModeTool {
444
465
  truncated: typeof value.originalBytes === 'number' &&
445
466
  value.originalBytes > Buffer.byteLength(value.value)
446
467
  };
468
+ if (typeof value.displayValue === 'string') {
469
+ if (Buffer.byteLength(value.displayValue) > 10_000)
470
+ throw new AgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Code Mode sent oversized display text.');
471
+ displayObservation = {
472
+ ...observation,
473
+ value: value.displayValue,
474
+ truncated: typeof value.originalBytes === 'number' &&
475
+ value.originalBytes > Buffer.byteLength(value.displayValue)
476
+ };
477
+ }
447
478
  }
448
479
  else
449
480
  throw new AgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Invalid observation frame.');
450
481
  appendObservation(observation, value.type === 'value' && typeof value.originalBytes === 'number'
451
482
  ? value.originalBytes
452
- : undefined);
483
+ : undefined, displayObservation);
453
484
  }
454
485
  else if (frame.kind === 'text' && typeof frame.value === 'string') {
455
486
  const originalBytes = frame.originalBytes ?? Buffer.byteLength(frame.value);
@@ -527,10 +558,11 @@ export class CodeModeTool {
527
558
  : undefined
528
559
  }
529
560
  });
530
- const { content, ...structured } = output;
561
+ const { eventData: _eventData, ...guestOutput } = output;
562
+ const { content, ...structured } = guestOutput;
531
563
  const projected = output.data !== undefined && content === JSON.stringify(output.data)
532
564
  ? structured
533
- : output;
565
+ : guestOutput;
534
566
  await write({ id, ok: true, output: projected });
535
567
  }
536
568
  catch (error) {
@@ -354,6 +354,7 @@ export async function createCodeModeEngine(options) {
354
354
  }
355
355
  const separatorBytes = textCalls ? 1 : 0;
356
356
  const capture = new BoundedTextOutput(Math.max(0, input.limits.outputBytes - outputBytes - separatorBytes), '');
357
+ const display = new BoundedTextOutput(10_000, '', 'tail');
357
358
  const emit = vm.newFunction('textChunk', (chunk) => {
358
359
  if (!chunk || vm.typeof(chunk) !== 'string')
359
360
  return {
@@ -366,7 +367,9 @@ export async function createCodeModeEngine(options) {
366
367
  return {
367
368
  error: guestError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Text serialization chunk limit exceeded.')
368
369
  };
369
- capture.append(vm.getString(chunk));
370
+ const text = vm.getString(chunk);
371
+ capture.append(text);
372
+ display.append(text);
370
373
  return vm.undefined;
371
374
  });
372
375
  const result = callGuest(formatText, vm.undefined, handle ?? vm.undefined, emit);
@@ -381,6 +384,7 @@ export async function createCodeModeEngine(options) {
381
384
  type: 'value',
382
385
  format: handle && vm.typeof(handle) === 'object' ? 'json' : 'text',
383
386
  value: captured.text,
387
+ displayValue: display.snapshot().text,
384
388
  originalBytes: captured.originalBytes
385
389
  });
386
390
  }
@@ -1,9 +1,10 @@
1
1
  export function renderCapturedText(snapshot: any, imageCount?: number): string;
2
2
  /** Bounded UTF-8 capture shared by trusted engines, the bridge and retained cells. */
3
3
  export class BoundedTextOutput {
4
- constructor(maximumBytes: any, separator?: string);
4
+ constructor(maximumBytes: any, separator?: string, retention?: string);
5
5
  maximumBytes: any;
6
6
  separator: string;
7
+ retention: string;
7
8
  append(value: any, originalBytes?: number): void;
8
9
  snapshot(): {
9
10
  text: string;
@@ -6,9 +6,10 @@ export class BoundedTextOutput {
6
6
  #retained = 0;
7
7
  #hasValue = false;
8
8
  #sealed = false;
9
- constructor(maximumBytes, separator = '\n') {
9
+ constructor(maximumBytes, separator = '\n', retention = 'head') {
10
10
  this.maximumBytes = maximumBytes;
11
11
  this.separator = separator;
12
+ this.retention = retention;
12
13
  }
13
14
  append(value, originalBytes = Buffer.byteLength(value)) {
14
15
  const separator = this.#hasValue ? this.separator : '';
@@ -16,10 +17,22 @@ export class BoundedTextOutput {
16
17
  this.#original += Buffer.byteLength(separator) + originalBytes;
17
18
  this.#capture(separator);
18
19
  this.#capture(value);
19
- if (originalBytes > Buffer.byteLength(value))
20
+ if (this.retention === 'head' && originalBytes > Buffer.byteLength(value))
20
21
  this.#sealed = true;
21
22
  }
22
23
  #capture(value) {
24
+ if (this.retention === 'tail') {
25
+ let start = Math.max(0, value.length - this.maximumBytes);
26
+ if (start > 0 && /[\uDC00-\uDFFF]/u.test(value[start]))
27
+ start++;
28
+ const bytes = Buffer.from(this.#chunks.join('') + value.slice(start));
29
+ let offset = Math.max(0, bytes.length - this.maximumBytes);
30
+ while (offset < bytes.length && (bytes[offset] & 0xc0) === 0x80)
31
+ offset++;
32
+ this.#chunks = [bytes.subarray(offset).toString('utf8')];
33
+ this.#retained = bytes.length - offset;
34
+ return;
35
+ }
23
36
  const remaining = Math.max(0, this.maximumBytes - this.#used);
24
37
  if (this.#sealed || !value.length)
25
38
  return;
@@ -9,7 +9,7 @@ export interface ClientToolDefinition extends ModelToolDefinition {
9
9
  outputSchema?: Record<string, unknown>;
10
10
  /** Runtime-only policy. MCP defaults to both callers; other tools default to direct only. */
11
11
  allowedCallers?: readonly ToolCaller[];
12
- /** Runtime-only allowlist of scalar fields in the public tool.started input. */
12
+ /** Runtime-only allowlist of scalars and bounded string arrays in public tool.started input. */
13
13
  eventInputFields?: readonly string[];
14
14
  /** Runtime-only exposure override; retains both permitted entry points with Code Mode enabled. */
15
15
  codeModeExposure?: 'both';
@@ -145,6 +145,9 @@ export interface CodeCellEventData {
145
145
  truncated?: boolean;
146
146
  /** Public output projection discarded older text; not inferred from source truncation. */
147
147
  outputHeadTruncated?: boolean;
148
+ /** Internal public-observation source fact, separate from local head clipping. */
149
+ outputSourceKnown?: boolean;
150
+ outputSourceTruncated?: boolean;
148
151
  observations?: readonly import('../session/code-observation-contract.js').CodeObservation[];
149
152
  error?: {
150
153
  code: string;
@@ -3,7 +3,7 @@ import { toolEventSummary } from '../runtime/event-projection.js';
3
3
  import { allowsToolCaller } from './callers.js';
4
4
  import { CodeCellExecutionError } from './code-mode/cells.js';
5
5
  import { createCodeToolDispatch } from './code-mode/dispatch.js';
6
- import { boundedModelToolOutput, publicToolEventInput, structuredToolArtifacts, publicExtensionToolEventData, structuredToolEventData, structuredToolFailureData } from './output-projection.js';
6
+ import { boundedModelToolOutput, publicToolEventInput, structuredToolArtifacts, publicExtensionToolEventData, publicOutputTruncation, publicEventOutputData, structuredToolEventData, structuredToolFailureData } from './output-projection.js';
7
7
  import { publishModelResources } from '../runtime/resource-publication.js';
8
8
  import { executeRegisteredTool } from './runtime.js';
9
9
  import { failedToolResultExtensions } from './result-extensions.js';
@@ -137,6 +137,7 @@ export async function executeRoundTools(calls, scope) {
137
137
  ? structuredToolEventData(call.name, output.data)
138
138
  : publicExtensionToolEventData(output.eventData);
139
139
  const artifacts = structuredToolArtifacts(output.artifacts);
140
+ const outputTruncation = publicOutputTruncation(call.name, structuredData);
140
141
  await scope.emit({
141
142
  type: 'tool.completed',
142
143
  ...(output.resultExtensions ? { resultExtensions: output.resultExtensions } : {}),
@@ -145,8 +146,10 @@ export async function executeRoundTools(calls, scope) {
145
146
  ...(parentCallId === undefined ? {} : { parentCallId }),
146
147
  summary: eventSummary.summary,
147
148
  truncated: output.truncated === true || modelOutput.truncated || eventSummary.truncated,
149
+ ...(eventSummary.truncated ? { summaryTruncated: true } : {}),
150
+ ...(outputTruncation ? { outputTruncation } : {}),
148
151
  ...(artifacts === undefined ? {} : { artifacts }),
149
- ...(structuredData === undefined ? {} : { data: structuredData })
152
+ ...(structuredData === undefined ? {} : { data: publicEventOutputData(structuredData) })
150
153
  });
151
154
  return {
152
155
  call,
@@ -173,6 +176,7 @@ export async function executeRoundTools(calls, scope) {
173
176
  (partial?.data === undefined
174
177
  ? undefined
175
178
  : structuredToolEventData(call.name, partial.eventData ?? partial.data));
179
+ const outputTruncation = publicOutputTruncation(call.name, eventData);
176
180
  await scope.emit({
177
181
  type: 'tool.failed',
178
182
  ...(failedToolResultExtensions(error)
@@ -186,7 +190,8 @@ export async function executeRoundTools(calls, scope) {
186
190
  ...(permissionStopFromError(error)
187
191
  ? { permissionStop: permissionStopFromError(error) }
188
192
  : {}),
189
- ...(eventData === undefined ? {} : { data: eventData })
193
+ ...(outputTruncation ? { outputTruncation } : {}),
194
+ ...(eventData === undefined ? {} : { data: publicEventOutputData(eventData) })
190
195
  });
191
196
  return {
192
197
  call,
@@ -12,6 +12,13 @@ export declare function boundedModelToolOutput(toolName: string, value: string,
12
12
  };
13
13
  /** The opted-in allowlist fails closed and never mutates the invocation arguments. */
14
14
  export declare function publicToolEventInput(value: unknown, fields: readonly string[]): Readonly<Record<string, unknown>>;
15
+ /** Explicit observations are public, but their event projection must remain bounded. */
16
+ export declare function publicCodeObservations(values: unknown, data: CodeCellEventData): CodeCellEventData;
17
+ /** Classify only facts about the public body, never model or summary limits. */
18
+ export declare function publicOutputTruncation(toolName: string, data: unknown): 'HEAD' | 'SOURCE' | undefined;
19
+ /** Remove the internal display direction from published structured data. */
20
+ export declare function publicEventOutputData<T>(data: T): T;
21
+ export declare function liveOutputTruncation(output: import('./contracts.js').ExecOutputSnapshot): 'HEAD' | 'SOURCE' | undefined;
15
22
  /** Project only known, validated tool result shapes into public structured events. */
16
23
  export declare function structuredToolEventData(toolName: string, value: unknown): CodeCellEventData | ExecResult | ApplyPatchResult | ReadEventData | ViewImageResult | SleepResult | PublicToolMetadata | undefined;
17
24
  /** Partial patch facts are allowlisted; arbitrary error details never become public event data. */
@@ -58,6 +58,9 @@ export function publicToolEventInput(value, fields) {
58
58
  .filter((key) => Object.hasOwn(value, key) &&
59
59
  (typeof value[key] === 'string' ||
60
60
  typeof value[key] === 'boolean' ||
61
+ (Array.isArray(value[key]) &&
62
+ value[key].length <= 100 &&
63
+ value[key].every((item) => typeof item === 'string')) ||
61
64
  (typeof value[key] === 'number' && Number.isFinite(value[key])) ||
62
65
  value[key] === null))
63
66
  .map((key) => [key, value[key]]));
@@ -68,7 +71,7 @@ export function publicToolEventInput(value, fields) {
68
71
  }
69
72
  }
70
73
  /** Explicit observations are public, but their event projection must remain bounded. */
71
- function publicCodeObservations(values, data) {
74
+ export function publicCodeObservations(values, data) {
72
75
  if (!Array.isArray(values))
73
76
  return data;
74
77
  const retained = new Set(data.observations ?? []);
@@ -77,6 +80,7 @@ function publicCodeObservations(values, data) {
77
80
  let remaining = 10_000;
78
81
  let truncated = false;
79
82
  let headTruncated = data.outputHeadTruncated === true;
83
+ let sourceTruncated = data.outputSourceTruncated === true;
80
84
  const clip = (text) => {
81
85
  let start = Math.max(0, text.length - remaining);
82
86
  if (start > 0 && /[\uDC00-\uDFFF]/u.test(text[start]))
@@ -101,6 +105,8 @@ function publicCodeObservations(values, data) {
101
105
  continue;
102
106
  }
103
107
  displayedCount++;
108
+ if (!data.outputSourceKnown && entry.truncated === true)
109
+ sourceTruncated = true;
104
110
  truncated ||= entry.truncated === true;
105
111
  const value = clip(entry.value);
106
112
  selected.unshift({
@@ -116,8 +122,11 @@ function publicCodeObservations(values, data) {
116
122
  else if (entry.type === 'tool_result' && isRecord(entry.source) && isRecord(entry.result)) {
117
123
  const name = entry.source.toolName;
118
124
  if (name !== 'exec' && name !== 'exec_wait') {
119
- if (retained.has(entry))
125
+ if (retained.has(entry)) {
126
+ if (!data.outputSourceKnown && entry.result.truncated === true)
127
+ sourceTruncated = true;
120
128
  selected.unshift(entry);
129
+ }
121
130
  }
122
131
  else if ((name === 'exec' || name === 'exec_wait') &&
123
132
  typeof entry.id === 'string' &&
@@ -129,10 +138,12 @@ function publicCodeObservations(values, data) {
129
138
  continue;
130
139
  }
131
140
  displayedCount++;
141
+ if (!data.outputSourceKnown && entry.result.truncated === true)
142
+ sourceTruncated = true;
132
143
  truncated ||= entry.result.truncated === true;
133
- const data = isRecord(entry.result.data) ? entry.result.data : undefined;
134
- const stderr = typeof data?.stderr === 'string' ? clip(data.stderr) : undefined;
135
- const stdout = typeof data?.stdout === 'string' ? clip(data.stdout) : undefined;
144
+ const resultData = isRecord(entry.result.data) ? entry.result.data : undefined;
145
+ const stderr = typeof resultData?.stderr === 'string' ? clip(resultData.stderr) : undefined;
146
+ const stdout = typeof resultData?.stdout === 'string' ? clip(resultData.stdout) : undefined;
136
147
  const content = stdout === undefined && stderr === undefined && typeof entry.result.content === 'string'
137
148
  ? clip(entry.result.content)
138
149
  : '';
@@ -153,10 +164,48 @@ function publicCodeObservations(values, data) {
153
164
  return {
154
165
  ...data,
155
166
  observations: selected,
167
+ outputSourceKnown: true,
168
+ ...(sourceTruncated ? { outputSourceTruncated: true } : {}),
156
169
  ...(truncated ? { truncated: true } : {}),
157
170
  ...(headTruncated ? { outputHeadTruncated: true } : {})
158
171
  };
159
172
  }
173
+ /** Classify only facts about the public body, never model or summary limits. */
174
+ export function publicOutputTruncation(toolName, data) {
175
+ if (!isRecord(data))
176
+ return undefined;
177
+ if (toolName === 'code_exec' || toolName === 'code_exec_wait') {
178
+ if (data.outputSourceTruncated === true)
179
+ return 'SOURCE';
180
+ if (data.outputHeadTruncated === true)
181
+ return 'HEAD';
182
+ }
183
+ if (toolName === 'exec' || toolName === 'exec_wait') {
184
+ const display = isRecord(data.displayOutput) ? data.displayOutput : undefined;
185
+ if (display)
186
+ return Number(display.stdoutDroppedBytes) > 0 || Number(display.stderrDroppedBytes) > 0
187
+ ? 'HEAD'
188
+ : undefined;
189
+ if (data.truncated === true)
190
+ return 'SOURCE';
191
+ }
192
+ return undefined;
193
+ }
194
+ /** Remove the internal display direction from published structured data. */
195
+ export function publicEventOutputData(data) {
196
+ if (!isRecord(data))
197
+ return data;
198
+ const { outputHeadTruncated: _head, outputSourceKnown: _known, outputSourceTruncated: _source, ...rest } = data;
199
+ return rest;
200
+ }
201
+ export function liveOutputTruncation(output) {
202
+ if (!output.truncated)
203
+ return undefined;
204
+ return output.displayOutput &&
205
+ (output.displayOutput.stdoutDroppedBytes > 0 || output.displayOutput.stderrDroppedBytes > 0)
206
+ ? 'HEAD'
207
+ : 'SOURCE';
208
+ }
160
209
  /** Project only known, validated tool result shapes into public structured events. */
161
210
  export function structuredToolEventData(toolName, value) {
162
211
  if (!isRecord(value))
@@ -199,6 +248,8 @@ export function structuredToolEventData(toolName, value) {
199
248
  : {}),
200
249
  status: value.status,
201
250
  ...(value.outputHeadTruncated === true ? { outputHeadTruncated: true } : {}),
251
+ ...(value.outputSourceKnown === true ? { outputSourceKnown: true } : {}),
252
+ ...(value.outputSourceTruncated === true ? { outputSourceTruncated: true } : {}),
202
253
  cached: value.cached === true,
203
254
  ...(['snapshot', 'completion', 'timeout', 'yield', 'interrupted'].includes(String(value.waitOutcome))
204
255
  ? { waitOutcome: value.waitOutcome }
@@ -2,7 +2,7 @@ import { AgentError } from '../error.js';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { AGENT_TOOL_HANDLERS, BUILTIN_TOOL_DEFINITIONS } from './registry.js';
4
4
  import { normalizeToolInput } from './input.js';
5
- import { validateToolResult } from './output-projection.js';
5
+ import { liveOutputTruncation, validateToolResult } from './output-projection.js';
6
6
  import { withToolResultExtensions } from './result-extensions.js';
7
7
  import { viewResourceImage } from './local/view-image.js';
8
8
  export async function executeRegisteredTool(name, arguments_, callId, context, caller = 'direct') {
@@ -58,6 +58,9 @@ export async function executeCapturedTool(kind, call, context) {
58
58
  toolName: name,
59
59
  callId,
60
60
  ...(context.parentCallId === undefined ? {} : { parentCallId: context.parentCallId }),
61
+ ...(liveOutputTruncation(output)
62
+ ? { outputTruncation: liveOutputTruncation(output) }
63
+ : {}),
61
64
  output
62
65
  })
63
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.11.43",
3
+ "version": "0.11.45",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",